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 ?? "") } }] },
},
});
}