Documentation menu
Guides / v0.1

Integration examples

Copy a complete TypeScript or Python workflow, or compose a pipeline with curl.

Reviewed September 16, 2026 · Current implementation

Choose an endpoint

Both examples use the REST API from a server or terminal. Supply a customer key with agents:read, query:write and query:read through your environment. List endpoints, select an id from the agents array, and set RECURSIFT_AGENT_ID to that value.

List accessible endpoints
curl --fail-with-body "https://api.recursift.app/v1/agents?limit=20" \
  -H "Authorization: Bearer $RECURSIFT_API_KEY"

TypeScript: submit and poll

Use Node.js 22 or newer. Save the example as query_endpoint.ts. It uses built-in fetch, typed receipts and findings, bounded rate-limit retries, and a five-second polling interval. It reports partial results and unanswered endpoints explicitly.

query_endpoint.ts
// TypeScript + Node.js 22 or newer. Run server-side, never in a browser.
import { randomUUID } from "node:crypto";
import { setTimeout as sleep } from "node:timers/promises";

const BASE = "https://api.recursift.app/v1";
type Receipt = {
  id: string;
  status: "running" | "complete" | "partial" | "timed_out" | "cancelled";
  agent_total: number;
  deadline: string;
};
type Job = Receipt & {
  findings: Array<{
    agent_id: string;
    status: string;
    answer: string | null;
    error: string | null;
    answered_at: string | null;
  }>;
  unanswered_agents: string[];
};

async function main() {
  const key = process.env.RECURSIFT_API_KEY;
  const agent = process.env.RECURSIFT_AGENT_ID;
  if (!key || !agent) {
    throw new Error("Set RECURSIFT_API_KEY and RECURSIFT_AGENT_ID first");
  }

  async function call<T>(path: string, payload?: unknown, requestId?: string): Promise<T> {
    for (let attempt = 0; attempt < 3; attempt++) {
      const response = await fetch(BASE + path, {
        method: payload === undefined ? "GET" : "POST",
        headers: {
          Authorization: "Bearer " + key,
          ...(payload === undefined ? {} : { "Content-Type": "application/json" }),
          ...(requestId ? { "Idempotency-Key": requestId } : {}),
        },
        body: payload === undefined ? undefined : JSON.stringify(payload),
        signal: AbortSignal.timeout(20_000),
        redirect: "error",
      });
      if (response.status === 429 && attempt < 2) {
        const seconds = Number(response.headers.get("Retry-After") ?? "5");
        await response.body?.cancel();
        await sleep((Number.isFinite(seconds) ? Math.min(60, Math.max(5, seconds)) : 5) * 1_000);
        continue;
      }
      if (!response.ok) {
        await response.body?.cancel();
        throw new Error("Recursift returned HTTP " + response.status);
      }
      return await response.json() as T;
    }
    throw new Error("Retry limit reached");
  }

  // Keep this ID unchanged if the submission is retried.
  const requestId = randomUUID();
  const receipt = await call<Receipt>("/queries", {
    question: "What operating system are you running?",
    target: { agent_ids: [agent] },
    timeout_seconds: 120,
  }, requestId);
  console.log("Query:", receipt.id);

  const end = performance.now() + 150_000;
  while (performance.now() < end) {
    const job = await call<Job>("/queries/" + encodeURIComponent(receipt.id));
    if (["complete", "partial", "timed_out", "cancelled"].includes(job.status)) {
      for (const finding of job.findings) {
        console.log(finding.agent_id, finding.status, finding.answer ?? finding.error);
      }
      console.log("Status:", job.status, "Unanswered:", job.unanswered_agents);
      return;
    }
    await sleep(5_000);
  }
  throw new Error("Stopped polling; retain the query ID to inspect it later");
}

main().catch((error: unknown) => {
  console.error(error instanceof Error ? error.message : "Query failed");
  process.exitCode = 1;
});
Compile and run in a Node.js project
npm install --save-dev typescript @types/node
npx tsc query_endpoint.ts --target ES2022 --module nodenext --strict --skipLibCheck --outDir dist
node dist/query_endpoint.js

Python: submit and poll

Set RECURSIFT_API_KEY and RECURSIFT_AGENT_ID in your environment. This standard-library example submits one question, uses a stable request key for rate-limit retries, polls for a bounded period, and keeps endpoint attribution and failures visible.

query_endpoint.py
# Python 3, standard library only. Run server-side or from your terminal.
import json
import os
import time
import urllib.error
import urllib.request
import uuid

BASE = "https://api.recursift.app/v1"
KEY = os.environ["RECURSIFT_API_KEY"]
AGENT = os.environ["RECURSIFT_AGENT_ID"]
request_id = str(uuid.uuid4())

def call(path, payload=None, request_id=None):
    headers = {"Authorization": "Bearer " + KEY}
    if request_id:
        headers["Idempotency-Key"] = request_id
    data = None
    if payload is not None:
        data = json.dumps(payload).encode()
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(BASE + path, data=data, headers=headers)
    # Bound retries; retain the same idempotency key for POST retries.
    for attempt in range(3):
        try:
            with urllib.request.urlopen(req, timeout=20) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            if error.code != 429 or attempt == 2:
                raise
            time.sleep(min(60, max(5, int(error.headers.get("Retry-After", "5")))))

job = call("/queries", {
    "question": "What operating system are you running?",
    "target": {"agent_ids": [AGENT]},
    "timeout_seconds": 120,
}, request_id)
print("Query:", job["id"])
end = time.monotonic() + 150
while time.monotonic() < end:
    job = call("/queries/" + job["id"])
    if job["status"] in {"complete", "partial", "timed_out", "cancelled"}:
        for finding in job["findings"]:
            print(finding["agent_id"], finding["status"],
                  finding["answer"] or finding["error"])
        print("Status:", job["status"], "Unanswered:", job["unanswered_agents"])
        break
    time.sleep(5)
else:
    raise TimeoutError("Stopped polling; retain the query ID to inspect it later")
Run with Python 3
python3 query_endpoint.py

Fit it into your existing stack

  • Inventory: page through /agents and store customer-scoped endpoint IDs in your integration.
  • Investigation: submit a bounded /queries job with explicit targets, then poll its ID.
  • Enrichment: attach each finding’s agent_id, answered_at, query_id and status to the corresponding investigation record.
  • Stored lookup: use /findings for earlier answer summaries before issuing another query.

Use a separate secret and separate stored results per customer. Do not present unanswered endpoints as clean. Webhooks, official language SDK packages and cross-customer bulk jobs are not available yet.

Continue readingQueries & evidence