← back to Ventura Claw

server/connectors/usaspending.js

47 lines

// USAspending.gov — federal awards / contracts / grants. No key.
// https://api.usaspending.gov/
const BASE = "https://api.usaspending.gov/api/v2";
async function call(method, path, body) {
  const r = await fetch(`${BASE}${path}`, {
    method, headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(25_000),
  });
  if (!r.ok) throw new Error(`usaspending ${r.status}: ${await r.text().then(t => t.slice(0, 180)).catch(()=>"")}`);
  return r.json();
}
module.exports = {
  meta: { id: "usaspending", name: "USAspending", category: "data", docsUrl: "https://api.usaspending.gov/", auth: "none", realImpl: true },
  fields: [],
  configured() { return true; },
  async health() {
    // Use a small, known-stable POST endpoint that doesn't require complex filters
    try {
      const d = await call("POST", "/search/spending_by_award_count/", {
        filters: { award_type_codes: ["A", "B", "C", "D"] },
      });
      return { ok: true, label: "live · federal awards/contracts/grants data" };
    } catch (e) { return { ok: false, reason: e.message }; }
  },
  actions: {
    async "search_awards"({ keyword, recipient, award_type, fiscal_year, limit }) {
      const filters = {};
      if (keyword) filters.keywords = [keyword];
      if (recipient) filters.recipient_search_text = [recipient];
      if (award_type) filters.award_type_codes = Array.isArray(award_type) ? award_type : [award_type];
      if (fiscal_year) filters.time_period = [{ start_date: `${fiscal_year}-10-01`, end_date: `${fiscal_year + 1}-09-30` }];
      const d = await call("POST", "/search/spending_by_award/", {
        filters,
        fields: ["Award ID", "Recipient Name", "Award Amount", "Award Type", "Awarding Agency", "Description", "Start Date", "End Date"],
        page: 1, limit: Math.min(limit || 25, 100),
      });
      return { total: d.results?.length || 0, awards: d.results || [] };
    },
    async "recipient_summary"({ duns_or_uei }) {
      if (!duns_or_uei) throw new Error("duns_or_uei required");
      const d = await call("GET", `/recipient/duns/${encodeURIComponent(duns_or_uei)}/`);
      return d;
    },
  },
};