RECIPES

The five things people build.

Every snippet here is run against the real API by our test suite, so a field that gets renamed breaks our build before it breaks your afternoon.

A. Poll for newly released confirmed changes

The recurring job. Keep a cursor, ask what is new, act on it. The feed is ordered by release date, which is the only ordering that works: C4 Original’s change window closed in February 2024 and the Signal was released in July 2026, so a feed ordered by the change would have buried it two years deep where no poller would ever see it.

CURL

curl -s "https://formulasignal.com/api/v1/signals?since=2026-08-01" \
  -H "Authorization: Bearer $FORMULASIGNAL_API_KEY" \
  | jq '.signals[] | {
      signal_id,
      product: .product.product_id,
      released_at: .review.released_at,
      dimension,
      window: .observation_window,
      cite: .signal_url
    }'

TYPESCRIPT

const BASE = "https://formulasignal.com";

async function newSignalsSince(cursorDate: string) {
  const seen: unknown[] = [];
  let url = `${BASE}/api/v1/signals?since=${cursorDate}`;

  while (url) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.FORMULASIGNAL_API_KEY}` },
    });

    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? 60);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }

    const body = await response.json();
    if (body.error) throw new Error(body.error.code);

    seen.push(...body.signals);
    // Follow the cursor, never construct one. A cursor we did not issue
    // is invalid_request rather than a silently reset page.
    url = body.next_cursor
      ? `${BASE}/api/v1/signals?cursor=${encodeURIComponent(body.next_cursor)}`
      : "";
  }

  return seen;
}

Store the newest review.released_at you have processed and pass it as since next run. Release dates never move, including when a finding is corrected, so nothing is delivered twice and a correction does not look like a new event. observation_window.exact_change_date is null on every Signal the Record cannot date to a day, which is most of them. signal_url is the permanent public address to cite.

B. Ground an AI answer

Resolve first, always. The model must never choose between adjacent products, and this is the call that stops it having to.

PYTHON

import os, requests

BASE = "https://formulasignal.com"
HEAD = {"Authorization": f"Bearer {os.environ['FORMULASIGNAL_API_KEY']}"}


def grounded_context(user_text: str):
    resolved = requests.post(
        f"{BASE}/api/v1/products/resolve",
        headers=HEAD, json={"identifier": user_text}, timeout=20,
    ).json()

    # Ambiguity is an answer, not a failure. Ask; do not pick.
    if resolved["status"] == "ambiguous":
        return {"ask_user": [c["name"] for c in resolved["candidates"]]}
    if resolved["status"] == "unsupported":
        return {"say": "FormulaSignal does not cover that product."}

    product_id = resolved["product"]["product_id"]
    record = requests.get(
        f"{BASE}/api/v1/products/{product_id}", headers=HEAD, timeout=20
    ).json()

    return {
        "declared": record["verified_facts"],
        "calculated_by_formulasignal": record["calculations"],
        "observed_on": record["record_as_of"],
        "sources": [
            {"url": s["url"], "read_on": s["observed_at"]}
            for s in record["source_references"]
        ],
        # Hand these to the model verbatim. They are the difference between
        # a cited answer and a confident wrong one.
        "must_state": record["limitations"],
    }

C. A product-change watcher

Two ways, and which one you want depends on whose products they are.

  • Any covered product, no extra scope: poll the Signal feed from recipe A and filter on product_id. This is the V1 event model and it is sufficient. FormulaSignal ships no webhooks: a confirmed change is a human-reviewed event that happens on the order of days, and a polling client with a stored cursor recovers from its own downtime, which a webhook consumer does not.
  • A person’s own watchlist, with email alerts: the three watch capabilities, which need watch:manage and an account binding. That scope is never on a self-service key. Ask us if you are building on behalf of a Founding Pro account.

WATCH MUTATIONS ARE IDEMPOTENT

# Adding a product already on the list is a success, not a duplicate.
# Retrying after a network failure is safe. So is an agent replaying itself.
curl -s -X POST https://formulasignal.com/api/v1/watch \
  -H "Authorization: Bearer $KEY_WITH_WATCH_SCOPE" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"cellucor-c4-original"}'

# Removing one that is not there is a success too. No payload names an
# account: the key is bound to exactly one, so acting on another is a
# shape the request cannot express.
curl -s -X DELETE https://formulasignal.com/api/v1/watch/cellucor-c4-original \
  -H "Authorization: Bearer $KEY_WITH_WATCH_SCOPE"

D. Current formula with its limits

CURL

curl -s https://formulasignal.com/api/v1/products/cellucor-c4-original \
  -H "Authorization: Bearer $FORMULASIGNAL_API_KEY" \
  | jq '{
      status,
      as_of: .record_as_of,
      declared: [.verified_facts[] | {
        ingredient: .value.declared_name,
        amount: .value.amount,
        unit: .value.unit
      }],
      calculated: .calculations,
      limits: .limitations
    }'

An amount of null means the label named the ingredient without saying how much. Do not coalesce it to zero. Zero is a stronger claim than the label makes and no source supports it. The evidence model has the rest of this.

E. Category intelligence for an entitled customer

The Category Ledger is the monthly B2B deliverable: an executive summary, confirmed changes released in the period, benchmarks with their cohorts, and the limitations. It needs ledger:read, which FormulaSignal grants against a named subscription.

# Identities first. A closed edition never changes, so this list and the
# edition itself are both safe to cache indefinitely.
curl -s https://formulasignal.com/api/v1/ledger \
  -H "Authorization: Bearer $LEDGER_KEY" | jq '.ledger.editions[]'

curl -s https://formulasignal.com/api/v1/ledger/2026-08 \
  -H "Authorization: Bearer $LEDGER_KEY"

Every proportion inside an edition names the cohort it was counted over. Quote the denominator with any figure you repeat: “28 of the 80 products disclosing an amount for every line” is the finding, and “35%” is not the same claim.

F. Find out what FormulaSignal does not know

The most under-used call. A product’s answer carries its own gaps, and knowing them is what lets you decide whether to publish.

curl -s https://formulasignal.com/api/v1/products/alpha-lion-superhuman-pre \
  -H "Authorization: Bearer $FORMULASIGNAL_API_KEY" \
  | jq '{status, confidence, limitations}'

That product answers every current-state question and is deliberately held below publishable depth, because the panel on file is served product-wide with no flavor in its asset name and flavor is a version dimension. The response says so rather than quietly presenting a guessed label as a measured one.