Call Your Agent from Your Product
You built an agent that does the job - in the app, from Claude, or with the SDK. This guide turns it into a backend feature of your own product using four plain HTTPS calls. Every step shows raw curl first and the Python equivalent next to it.
The whole integration is:
- Upload a file the run should work on (optional) - get back a
public_url - Start a run - a frozen compilation via
run-instruction(the production default) or a live agent viarun-loop - Poll the execution every ~2 seconds until it reaches a terminal status
- Read
agent_result
A run takes roughly tens of seconds - the agent plans, calls tools, and assembles a result. Fire and forget from your hot path: the POST that starts the run returns immediately with an execution id. Store that id and poll from a background job. Never make an end-user request wait synchronously on a run.
The contract at a glance
Base URL is https://backend.flymy.ai. Auth is the same X-API-KEY: fly-*** header on every call - get a key at app.flymy.ai/profile.
| Call | Purpose |
|---|---|
POST /api/v1/agents/agent-file-chat-upload/ | Multipart upload (file, external_id) - returns {"public_url"} |
POST /api/v1/agents/tasks/{agent_uuid}/run-loop/ | Start a run on a live agent with {"variables": {...}} - returns {"id"} |
POST /api/v1/agents/compilations/{compilation_id}/run-instruction/ | Start a run of a frozen compilation with {"variables": {...}} - returns a new run |
GET /api/v1/agents/executions/{id}/ | Current run state - poll until completed / failed, then read agent_result |
POST /api/v1/agents/compilations/freeze-instruction/{execution_id}/ | Freeze a good run into a compilation you can run-instruction from then on |
You address a live agent by its UUID - grab it from client.agents.list() in the Python SDK, from the list_agents gateway tool, or from the agent's chat URL in the app (app.flymy.ai/agents/chat/{agent_uuid}).
The running example below is an agent that turns a voice memo into a structured note - upload the audio, run, poll, render the note in your UI.
1. Upload input files (optional)
Skip this step if your run only needs text variables. When the run needs a file from your product - an audio memo, an image, a document - upload it first and pass the returned public_url to the run as a variable.
- curl
- Python
curl -X POST https://backend.flymy.ai/api/v1/agents/agent-file-chat-upload/ \
-H "X-API-KEY: $FLYMYAI_API_KEY" \
-F "file=@voice-memo.m4a" \
-F "external_id=user-4242"
import requests
resp = requests.post(
"https://backend.flymy.ai/api/v1/agents/agent-file-chat-upload/",
headers={"X-API-KEY": "fly-***"},
files={"file": open("voice-memo.m4a", "rb")},
data={"external_id": "user-4242"},
)
public_url = resp.json()["public_url"]
external_id is an identifier you choose on your side (a user or session id works well). The response carries the field you need:
{"public_url": "https://..."}
2. Start the run
There are two ways to start a run, and they matter differently in production:
- Frozen compilation (
run-instruction) - replays a fixed instruction distilled from a run you approved. Deterministic, cheaper, faster. This is what you want behind a product feature. If you don't have a compilation id yet, see Freeze your agent below. - Live agent (
run-loop) - the agent re-plans from its goal on every run. Right for interactive use and for producing the good run you will freeze.
- curl
- Python
# Production default: run a frozen compilation
curl -X POST https://backend.flymy.ai/api/v1/agents/compilations/$COMPILATION_ID/run-instruction/ \
-H "X-API-KEY: $FLYMYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"variables": {"audio_url": "'"$PUBLIC_URL"'"}}'
# Or: run a live agent by UUID
curl -X POST https://backend.flymy.ai/api/v1/agents/tasks/$AGENT_UUID/run-loop/ \
-H "X-API-KEY: $FLYMYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"variables": {"audio_url": "'"$PUBLIC_URL"'"}}'
from flymyai import AgentClient
client = AgentClient(api_key="fly-***")
# Production default: run a frozen compilation
run = client.compilations.run_instruction(
compilation_id,
variables={"audio_url": public_url},
)
# Or: run a live agent by UUID
run = client.runs.create(
agent_id=agent_uuid,
variables={"audio_url": public_url},
)
Both calls return immediately - the run executes asynchronously on the server. The response gives you the execution id (a string slug):
{"id": "won-gsfr-mxp"}
variables are validated against the agent's input_schema and rendered into the goal for this run - see Inputs, Outputs & Variables.
3. Poll until it finishes, read agent_result
Poll GET /api/v1/agents/executions/{id}/ about every 2 seconds until status is terminal (completed, failed, or cancelled), then read agent_result.
- curl
- Python
EXECUTION_ID="won-gsfr-mxp"
while :; do
RUN=$(curl -s https://backend.flymy.ai/api/v1/agents/executions/$EXECUTION_ID/ \
-H "X-API-KEY: $FLYMYAI_API_KEY")
STATUS=$(echo "$RUN" | jq -r .status)
echo "status: $STATUS"
case "$STATUS" in completed|failed|cancelled) break ;; esac
sleep 2
done
echo "$RUN" | jq .agent_result
# SDK: polls every 2 seconds by default
result = client.runs.wait(run.id, timeout=600, poll_interval=2.0)
print(result.output) # alias for agent_result
# Frozen one-liner: start + poll in a single call
result = client.compilations.run_instruction_and_wait(
compilation_id,
variables={"audio_url": public_url},
timeout=600,
)
# No SDK? The same loop in plain requests:
import time, requests
def wait_for_result(execution_id: str, timeout: float = 600.0) -> dict:
headers = {"X-API-KEY": "fly-***"}
url = f"https://backend.flymy.ai/api/v1/agents/executions/{execution_id}/"
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
run = requests.get(url, headers=headers).json()
if run["status"] in ("completed", "failed", "cancelled"):
return run
time.sleep(2)
raise TimeoutError(f"run {execution_id} did not finish in {timeout}s")
A completed execution looks like this (trimmed to the fields you need):
{
"id": "won-gsfr-mxp",
"status": "completed",
"agent_result": {
"title": "Standup notes",
"summary": "Shipped the importer, blocked on billing review...",
"action_items": ["Ping legal about the DPA", "Cut release v2.3"]
}
}
The shape of agent_result is whatever the agent's output_schema defines, so your product code can rely on the fields being there. On failed, the run carries a plain error string instead (surfaced as .error in the SDK) - see Runs for the full lifecycle.
Runs take tens of seconds; polling every 2 seconds finds the result within moments of it landing. Hammering the endpoint more often does not make the agent finish sooner.
4. Freeze your agent for production
A live agent re-plans on every run - great while you iterate, wasteful once the pipeline is settled. Freezing distills a run you approved into a fixed Markdown instruction that future runs replay: deterministic, cheaper, faster. ("Freeze" and "compile" are the same operation; the SDK surface is client.compilations.)
The flow:
- Run the agent live (in the app chat or via
run-loop) until one run does exactly what you want. - Freeze that execution:
- curl
- Python
curl -X POST https://backend.flymy.ai/api/v1/agents/compilations/freeze-instruction/$EXECUTION_ID/ \
-H "X-API-KEY: $FLYMYAI_API_KEY"
# Freeze + poll until the compilation reaches "compiled"
compilation = client.agents.compile_from_run(run.id, timeout=120)
print(compilation.id) # use this in run-instruction
print(compilation.instruction_md) # the frozen plan
- The freeze call returns immediately and compilation starts in the background (status begins at
pending). The SDK helpercompile_from_runwraps the polling; seeclient.compilationsfor the lower-level building blocks. - Point your production traffic at
run-instructionwith the new compilation id (step 2 above).
You can also freeze without touching REST at all: from any MCP client connected to the FlyMy MCPs gateway, the freeze_agent and run_frozen tools do the same thing conversationally.
A frozen compilation replays a fixed instruction. A model update never changes a frozen endpoint - your product gets the same pipeline tomorrow that you approved today. When you want new behavior, you cut a new version deliberately: freeze a newer run, get a new compilation id, and switch your config when you are ready.
Production notes
Fire and forget from your hot path. The only synchronous call in your request handler is the POST that starts the run - it returns the execution id in milliseconds. Persist the id, return to your user, and let a background worker poll every ~2 seconds until the run lands. Render agent_result when it does.
Runs can pause for approval. If the agent's goal contains approval rules ("ask me before sending anything external"), a run can pause mid-flight and raise an approval request in the app at app.flymy.ai; it resumes after the owner approves. From your product's side this is just a longer-running execution - keep polling and do not treat a slow run as a failure. See Human-in-the-Loop Approvals.
Cost is per run, per use. There is no subscription - each run bills for what it actually used. The gateway tools get_execution_price and list_execution_prices return the real billed cost per run, not an estimate. As a reference point from real bills: a dictation-note pipeline like the example above lands at $0.031 per run.
Schedules instead of on-demand. If the same frozen run should fire on a schedule rather than per request, set a standard 5-field cron expression (plus an optional IANA timezone) on the compilation - compilations.update in the SDK or the schedule_agent gateway tool. The schedule fires per compilation, and each scheduled run appears as a normal execution you can poll like any other. See scheduling in the SDK reference.
Next steps
- Runs - lifecycle, event streaming, freeze semantics in depth
- Inputs, Outputs & Variables - schemas that shape
variablesandagent_result - Python SDK - the same contract with polling, validation, and error handling built in
- Connect Claude & Any MCP Client - drive the same agents conversationally through the gateway