Short, copy-pasteable workflows that combine a few endpoints. All examples assume fr_your_api_key and a known project_id.

Generate a handoff for only the main branch of one repo, then poll for the result.

# 1. Enqueue
curl "https://www.flowrelay.it/api/v1/handoffs" \
  -H "Authorization: Bearer fr_your_api_key" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "project_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "sources": ["github"], "filters": { "github": { "branches": ["main"] } } }'
 
# 2. Poll (repeat until status is completed)
curl "https://www.flowrelay.it/api/v1/jobs/c3d4e5f6-a7b8-9012-cdef-123456789012" \
  -H "Authorization: Bearer fr_your_api_key"

Discover valid branch and repo values first with GET /handoffs/filters – see Filters.

Release notes are generated like any other insight – same endpoint, same job polling – and land in GET /projects/{id}/insights with kind=release_notes. Pass style: "pr_description" for a single pull-request write-up instead. The window is fixed at the last 14 days, so lookbackDays and maxEvents are rejected here.

# 1. Enqueue
curl "https://www.flowrelay.it/api/v1/projects/3fa85f64-5717-4562-b3fc-2c963f66afa6/insights/release_notes" \
  -H "Authorization: Bearer fr_your_api_key" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "source": "github", "repo": "acme/payments", "style": "release_notes" }'
 
# 2. Poll until completed, then read result.markdown
curl "https://www.flowrelay.it/api/v1/jobs/e7f8a9b0-1c2d-3e4f-5a6b-7c8d9e0f1a2b" \
  -H "Authorization: Bearer fr_your_api_key"

Add sources to bring another source into the same write-up – a build source alongside the repository, say – and filters to narrow it further (for example one branch).

POST /projects/{id}/qa answers in the response – no job, no polling. The answer cites the events it used as ev: plus the first 8 characters of the event id, which you can resolve through GET /events.

curl "https://www.flowrelay.it/api/v1/projects/3fa85f64-5717-4562-b3fc-2c963f66afa6/qa" \
  -H "Authorization: Bearer fr_your_api_key" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "question": "Why did we move checkout off the legacy queue?" }'
{
  "answer": "Checkout moved off the legacy queue because the retry semantics could double-charge on a partial failure…",
  "citations": ["ev:a1b2c3d4", "ev:9f8e7d6c"]
}

Add filters to narrow the activity considered – the same shape used by handoffs and insights. Long questions can exceed the 60s budget and return 504; narrow the question and retry.

Digests are generated on the project's own cadence (daily or weekly, configured in the dashboard), so there is nothing to enqueue – you only read them back, newest first.

curl "https://www.flowrelay.it/api/v1/projects/3fa85f64-5717-4562-b3fc-2c963f66afa6/digests?limit=5" \
  -H "Authorization: Bearer fr_your_api_key"

Each row carries periodStart / periodEnd and a markdown field with the canonical rendering.

List active resources that are producing events but are not yet assigned to any project, so you can decide what to add.

curl "https://www.flowrelay.it/api/v1/integrations/untracked" \
  -H "Authorization: Bearer fr_your_api_key"
[
  { "source": "slack", "resource_id": "C0123", "resource_name": "#incidents", "event_count": 42 }
]

The response is a bare array sorted by most recent activity.

Send a message to a channel in your connected Discord server. Pass content for plain text, or send a Flow Relay artifact as a .md attachment.

async function announce(apiKey, channelId, text) {
  const res = await fetch("https://www.flowrelay.it/api/v1/discord/send", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ channel_id: channelId, content: text }),
  });
  return res.json();
}

To post a rendered artifact instead, send handoff_id / insight_id, or artifact with project_id. The artifact is rendered to the same Markdown as the dashboard copy button and attached as a .md file:

curl "https://www.flowrelay.it/api/v1/discord/send" \
  -H "Authorization: Bearer fr_your_api_key" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "channel_id": "987654321098765432", "artifact": "last_handoff", "project_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }'

List channel ids first with GET /discord/channels. The target channel must belong to your connected guild, or the call returns 403.

Chain the two: generate a handoff, wait for it, then post a link.

import requests, time
 
BASE = "https://www.flowrelay.it/api/v1"
HEADERS = {"Authorization": "Bearer fr_your_api_key"}
 
job = requests.post(
    f"{BASE}/handoffs", headers=HEADERS, json={"project_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"}
).json()
 
job_id = job["jobId"]
while True:
    payload = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS).json()
    if payload["job"]["status"] in ("completed", "failed"):
        break
    time.sleep(2)
 
handoff = payload["result"]
requests.post(
    f"{BASE}/discord/send",
    headers=HEADERS,
    json={"channel_id": "987654", "content": f"New handoff: {handoff['title']}"},
)

See Async jobs for a hardened polling loop with a timeout.