Recipes

Common workflows. Copy + paste, swap in your keys.

Daily Slack digest

Pulls the latest validated briefs from GET /v1/briefs and posts headline + lede to a channel.

typescript
import { ForumAtlas } from "@forumatlas/sdk";
import { WebClient } from "@slack/web-api";

const forum = new ForumAtlas({ apiKey: process.env.FORUMATLAS_API_KEY! });
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

export async function dailyDigest() {
  const { briefs } = await forum.briefs.list({ sector: "cyber", limit: 3 });
  const validated = briefs.filter((b) => b.status === "validated");

  const blocks = validated.flatMap((b) => [
    { type: "header", text: { type: "plain_text", text: b.headline } },
    { type: "section", text: { type: "mrkdwn", text: b.lede ?? "" } },
  ]);

  await slack.chat.postMessage({ channel: "#forum-daily", blocks });
}

Anomaly alerts into your own pipeline

Register a webhook with POST /v1/anomalies/subscribe, then verify the X-Forum-Signature HMAC on every delivery. Note: subscriptions are stored immediately, but delivery runs out-of-band on the anomaly digest worker's schedule — this is a digest feed, not an instant push (see Webhooks for the current delivery status).

typescript
import crypto from "node:crypto";
import { ForumAtlas } from "@forumatlas/sdk";

const forum = new ForumAtlas({ apiKey: process.env.FORUMATLAS_API_KEY! });

// One-time setup — sectors is an array; webhook_secret must be >= 24 chars.
const { subscription_id } = await forum.anomalies.subscribe({
  sectors: ["energy", "finance"],
  anomaly_types: ["regulator_vocabulary_shift", "abnormal_earnings_tone"],
  delivery_channels: ["webhook"],
  webhook_url: "https://your-app.com/webhooks/forum-anomalies",
  webhook_secret: process.env.FORUM_WEBHOOK_SECRET!, // >= 24 chars
});

// In your webhook handler: verify before processing.
// (Compare lengths first — timingSafeEqual THROWS on unequal-length buffers,
// so a malformed signature would 500 your handler instead of returning false.)
export function verifySignature(rawBody: string, signature: string) {
  const expected = crypto
    .createHmac("sha256", process.env.FORUM_WEBHOOK_SECRET!)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Track forecast movement in Notion

There is no push feed for forecast revisions — poll GET /v1/forecasts on a schedule and upsert rows keyed by forecast_id.

typescript
const { forecasts } = await forum.forecasts.list({ sector: "finance", limit: 20 });

for (const f of forecasts) {
  await notion.pages.create({
    parent: { database_id: process.env.NOTION_FORECASTS_DB! },
    properties: {
      Title: { title: [{ text: { content: String(f.subject_program ?? f.forecast_id) } }] },
      Probability: { number: Number(f.point_estimate ?? 0) },
      Horizon: { rich_text: [{ text: { content: String(f.horizon_label ?? "") } }] },
    },
  });
}