← back to Norma
agents/onboard-agent/lib/propublica.js
82 lines
/**
* ProPublica Nonprofit Explorer API Wrapper
*
* Searches and retrieves nonprofit organization data from ProPublica's
* Nonprofit Explorer API v2.
*/
const fetch = require('node-fetch');
const BASE_URL = 'https://projects.propublica.org/nonprofits/api/v2';
/**
* Search for nonprofits by keyword query.
*
* @param {string} query - Search query (org name, keyword, etc.)
* @param {number} [page=0] - Page number (0-indexed)
* @returns {Promise<{ organizations: Array, total: number }>}
*/
async function searchNonprofits(query, page = 0) {
const url = `${BASE_URL}/search.json?q=${encodeURIComponent(query)}&page=${page}`;
try {
const res = await fetch(url, {
headers: { 'User-Agent': 'Norma-OnBoard-Agent/1.0' },
timeout: 15000,
});
if (!res.ok) {
console.error(`[propublica] Search failed (${res.status}): ${url}`);
return { organizations: [], total: 0 };
}
const data = await res.json();
return {
organizations: (data.organizations || []).map(org => ({
ein: org.ein,
name: org.name,
city: org.city,
state: org.state,
ntee_code: org.ntee_code,
subsection_code: org.subsection_code,
total_revenue: org.total_revenue,
total_assets: org.total_assets,
})),
total: data.total_results || 0,
};
} catch (err) {
console.error(`[propublica] Search error for "${query}":`, err.message);
return { organizations: [], total: 0 };
}
}
/**
* Get detailed info for a specific organization by EIN.
*
* @param {string} ein - Employer Identification Number
* @returns {Promise<Object|null>}
*/
async function getOrgDetails(ein) {
const url = `${BASE_URL}/organizations/${ein}.json`;
try {
const res = await fetch(url, {
headers: { 'User-Agent': 'Norma-OnBoard-Agent/1.0' },
timeout: 15000,
});
if (!res.ok) {
console.error(`[propublica] Org details failed (${res.status}): EIN ${ein}`);
return null;
}
return await res.json();
} catch (err) {
console.error(`[propublica] Org details error for EIN ${ein}:`, err.message);
return null;
}
}
module.exports = { searchNonprofits, getOrgDetails };