The Async Submit → Poll Model
Every stage of the InvesTeam pipeline that does real work is asynchronous. You
submit work and get back a session_id and a status; you then poll a
matching read route until it reaches a terminal state. One session_id threads
the whole run — brief, orchestration, execution, and analysis all share it. Learn
this one pattern and every endpoint reads the same way.
Why does InvesTeam submit then poll?¶
Because a committee run takes minutes, not milliseconds — a blocking call would hold a connection open for the length of the analysis. Instead, a submit returns immediately with an id, and you poll at your own cadence. Nothing on your side waits on a long-lived request, and a client that drops can re-attach to the run simply by polling the same id.
The spine is one pattern repeated across the pipeline:
POST /api/v1/briefs → session_id + status
poll GET /api/v1/briefs/{id} until completed | rejected
(if needs_clarification) POST /api/v1/briefs/{id}/answers → re-poll
POST /api/v1/orchestrations → convene the committee
poll GET /api/v1/orchestrations/{id} for the committee + task plan
poll GET /api/v1/executions/{id} until completed | failed
stream GET /api/v1/executions/{id}/transcript?after=<seq>
poll GET /api/v1/analyses/{id} for the final analysis
How do I poll for a result?¶
Fetch the stage's read route on a sane interval — every two to three seconds is a
good default — until its status is terminal. Read the status field on each poll
and stop when it settles (for a brief, completed or rejected; for an
execution, completed or failed).
curl -sS https://investeam.io/api/v1/briefs/SESSION_ID \
-H "Authorization: Bearer hfk_your_key_here"
import time
import requests
url = "https://investeam.io/api/v1/briefs/SESSION_ID"
headers = {"Authorization": "Bearer hfk_your_key_here"}
while True:
resp = requests.get(url, headers=headers)
status = resp.json().get("status")
if status in ("completed", "rejected"):
break
time.sleep(2.5)
print(status)
Poll no faster than the limits allow — see Rate Limits & Quotas.
What does a 404 mean on an InvesTeam poll?¶
On the downstream poll routes — GET /api/v1/orchestrations/{id},
GET /api/v1/executions/{id}, GET /api/v1/executions/{id}/transcript, and
GET /api/v1/analyses/{id} — a 404 means "valid id, this stage hasn't
produced output yet." It is not a hard error; keep polling.
A 404 on GET /api/v1/briefs/{id} is different. A brief that exists is always
readable by its owner, so a 404 there means the id is unknown or foreign — a
genuine not_found, not a not-ready signal. This is the one poll route where
404 is terminal.
Treat status and transcript
kind as an open set. If you receive a value you do
not recognize, treat it as non-terminal and keep polling — the API may add states
within v1. See Versioning &
Deprecation.
Do I need an idempotency key?¶
Send an Idempotency-Key header on every submit — POST /api/v1/briefs,
POST /api/v1/briefs/{id}/answers, and POST /api/v1/orchestrations — so a
retry can never double-submit. The same key with the same request body returns
the original result and starts no second run; the same key with a different
body returns 409 conflict, so a buggy retry cannot silently overwrite.
curl -sS https://investeam.io/api/v1/briefs \
-H "Authorization: Bearer hfk_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f2c1e7a-brief-001" \
-d '{"input": "Is NVDA a buy right now?"}'
import requests
resp = requests.post(
"https://investeam.io/api/v1/briefs",
headers={
"Authorization": "Bearer hfk_your_key_here",
"Idempotency-Key": "9f2c1e7a-brief-001",
},
json={"input": "Is NVDA a buy right now?"},
)
print(resp.status_code, resp.json())
Generate one key per logical submit — a UUID is ideal. Because submits are the only calls that spend a kickoff, the idempotency key is your protection against a retry storm multiplying cost.
When should I retry, and when should I not?¶
Let the error envelope decide. Every non-2xx response carries a retryable flag:
client-caused errors (bad_request, unauthorized, forbidden, not_found,
conflict) are false — fix the request, do not retry blindly — while
upstream_error, internal_error, and rate_limited are true — retry with
back-off. On a 429, wait the advertised Retry-After interval. See the
Error Reference and Rate Limits & Quotas.
Where next?¶
See the whole pattern run once in the Pipeline Walkthrough, or go straight to the Briefs reference for the first submit.