@fedpulse/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/cjs/client.cjs +138 -0
- package/dist/cjs/errors.cjs +200 -0
- package/dist/cjs/http.cjs +449 -0
- package/dist/cjs/index.cjs +65 -0
- package/dist/cjs/resources/analytics.cjs +134 -0
- package/dist/cjs/resources/assistance.cjs +101 -0
- package/dist/cjs/resources/entities.cjs +149 -0
- package/dist/cjs/resources/exclusions.cjs +135 -0
- package/dist/cjs/resources/intelligence.cjs +96 -0
- package/dist/cjs/resources/opportunities.cjs +170 -0
- package/dist/cjs/resources/webhooks.cjs +262 -0
- package/dist/cjs/types/analytics.cjs +5 -0
- package/dist/cjs/types/assistance.cjs +5 -0
- package/dist/cjs/types/common.cjs +5 -0
- package/dist/cjs/types/entities.cjs +5 -0
- package/dist/cjs/types/exclusions.cjs +5 -0
- package/dist/cjs/types/index.cjs +5 -0
- package/dist/cjs/types/intelligence.cjs +5 -0
- package/dist/cjs/types/opportunities.cjs +5 -0
- package/dist/cjs/types/webhooks.cjs +5 -0
- package/dist/cjs/webhooks-verify.cjs +184 -0
- package/dist/esm/client.js +135 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/errors.js +187 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/http.js +445 -0
- package/dist/esm/http.js.map +1 -0
- package/dist/esm/index.js +40 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/resources/analytics.js +131 -0
- package/dist/esm/resources/analytics.js.map +1 -0
- package/dist/esm/resources/assistance.js +98 -0
- package/dist/esm/resources/assistance.js.map +1 -0
- package/dist/esm/resources/entities.js +146 -0
- package/dist/esm/resources/entities.js.map +1 -0
- package/dist/esm/resources/exclusions.js +132 -0
- package/dist/esm/resources/exclusions.js.map +1 -0
- package/dist/esm/resources/intelligence.js +93 -0
- package/dist/esm/resources/intelligence.js.map +1 -0
- package/dist/esm/resources/opportunities.js +167 -0
- package/dist/esm/resources/opportunities.js.map +1 -0
- package/dist/esm/resources/webhooks.js +259 -0
- package/dist/esm/resources/webhooks.js.map +1 -0
- package/dist/esm/types/analytics.js +5 -0
- package/dist/esm/types/analytics.js.map +1 -0
- package/dist/esm/types/assistance.js +5 -0
- package/dist/esm/types/assistance.js.map +1 -0
- package/dist/esm/types/common.js +5 -0
- package/dist/esm/types/common.js.map +1 -0
- package/dist/esm/types/entities.js +5 -0
- package/dist/esm/types/entities.js.map +1 -0
- package/dist/esm/types/exclusions.js +5 -0
- package/dist/esm/types/exclusions.js.map +1 -0
- package/dist/esm/types/index.js +5 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/intelligence.js +5 -0
- package/dist/esm/types/intelligence.js.map +1 -0
- package/dist/esm/types/opportunities.js +5 -0
- package/dist/esm/types/opportunities.js.map +1 -0
- package/dist/esm/types/webhooks.js +5 -0
- package/dist/esm/types/webhooks.js.map +1 -0
- package/dist/esm/webhooks-verify.js +179 -0
- package/dist/esm/webhooks-verify.js.map +1 -0
- package/dist/types/client.d.cts +136 -0
- package/dist/types/client.d.ts +136 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/errors.d.cts +139 -0
- package/dist/types/errors.d.ts +139 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/http.d.cts +137 -0
- package/dist/types/http.d.ts +137 -0
- package/dist/types/http.d.ts.map +1 -0
- package/dist/types/index.d.cts +39 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/resources/analytics.d.cts +94 -0
- package/dist/types/resources/analytics.d.ts +94 -0
- package/dist/types/resources/analytics.d.ts.map +1 -0
- package/dist/types/resources/assistance.d.cts +66 -0
- package/dist/types/resources/assistance.d.ts +66 -0
- package/dist/types/resources/assistance.d.ts.map +1 -0
- package/dist/types/resources/entities.d.cts +101 -0
- package/dist/types/resources/entities.d.ts +101 -0
- package/dist/types/resources/entities.d.ts.map +1 -0
- package/dist/types/resources/exclusions.d.cts +84 -0
- package/dist/types/resources/exclusions.d.ts +84 -0
- package/dist/types/resources/exclusions.d.ts.map +1 -0
- package/dist/types/resources/intelligence.d.cts +66 -0
- package/dist/types/resources/intelligence.d.ts +66 -0
- package/dist/types/resources/intelligence.d.ts.map +1 -0
- package/dist/types/resources/opportunities.d.cts +116 -0
- package/dist/types/resources/opportunities.d.ts +116 -0
- package/dist/types/resources/opportunities.d.ts.map +1 -0
- package/dist/types/resources/webhooks.d.cts +180 -0
- package/dist/types/resources/webhooks.d.ts +180 -0
- package/dist/types/resources/webhooks.d.ts.map +1 -0
- package/dist/types/types/analytics.d.cts +85 -0
- package/dist/types/types/analytics.d.ts +85 -0
- package/dist/types/types/analytics.d.ts.map +1 -0
- package/dist/types/types/assistance.d.cts +55 -0
- package/dist/types/types/assistance.d.ts +55 -0
- package/dist/types/types/assistance.d.ts.map +1 -0
- package/dist/types/types/common.d.cts +58 -0
- package/dist/types/types/common.d.ts +58 -0
- package/dist/types/types/common.d.ts.map +1 -0
- package/dist/types/types/entities.d.cts +85 -0
- package/dist/types/types/entities.d.ts +85 -0
- package/dist/types/types/entities.d.ts.map +1 -0
- package/dist/types/types/exclusions.d.cts +81 -0
- package/dist/types/types/exclusions.d.ts +81 -0
- package/dist/types/types/exclusions.d.ts.map +1 -0
- package/dist/types/types/index.d.cts +12 -0
- package/dist/types/types/index.d.ts +12 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/intelligence.d.cts +104 -0
- package/dist/types/types/intelligence.d.ts +104 -0
- package/dist/types/types/intelligence.d.ts.map +1 -0
- package/dist/types/types/opportunities.d.cts +149 -0
- package/dist/types/types/opportunities.d.ts +149 -0
- package/dist/types/types/opportunities.d.ts.map +1 -0
- package/dist/types/types/webhooks.d.cts +106 -0
- package/dist/types/types/webhooks.d.ts +106 -0
- package/dist/types/types/webhooks.d.ts.map +1 -0
- package/dist/types/webhooks-verify.d.cts +102 -0
- package/dist/types/webhooks-verify.d.ts +102 -0
- package/dist/types/webhooks-verify.d.ts.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FedPulse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# @fedpulse/sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fedpulse/sdk)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Official JavaScript / TypeScript SDK for the [FedPulse API](https://fedpulse.dev). Search federal contract opportunities, check vendor exclusions, retrieve entity registrations, and access market intelligence — all with zero runtime dependencies.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Typed end-to-end** — full TypeScript definitions for every request and response
|
|
14
|
+
- **Dual ESM + CJS** — works in Node.js (ESM / CommonJS), Deno, Bun, and bundlers
|
|
15
|
+
- **Zero runtime dependencies** — only uses Node.js built-ins (`fetch`, `crypto`)
|
|
16
|
+
- **Automatic retry** — exponential back-off with full jitter for 5xx / network errors
|
|
17
|
+
- **In-memory LRU cache** — 256-entry, 60 s TTL on all GET requests
|
|
18
|
+
- **Webhook verification** — HMAC-SHA256 signature + replay-attack protection
|
|
19
|
+
- **Cursor + offset pagination** — async generator helpers for all list endpoints
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Node.js **≥ 18.0.0** (native `fetch` + `crypto`)
|
|
26
|
+
- A FedPulse API key — generate one at [app.fedpulse.dev/dashboard](https://app.fedpulse.dev/dashboard)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @fedpulse/sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { FedPulse } from '@fedpulse/sdk';
|
|
42
|
+
|
|
43
|
+
const client = new FedPulse({ apiKey: process.env.FEDPULSE_API_KEY! });
|
|
44
|
+
|
|
45
|
+
// Search opportunities
|
|
46
|
+
const result = await client.opportunities.list({
|
|
47
|
+
keyword: 'cybersecurity',
|
|
48
|
+
naics: ['541519', '541512'],
|
|
49
|
+
postedAfter: '2025-01-01',
|
|
50
|
+
limit: 25,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(result.data); // typed OpportunitySummary[]
|
|
54
|
+
console.log(result.pagination); // { total, page, limit, … }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
Pass your API key to the constructor. It is sent as `Authorization: Bearer <key>` on every request.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const client = new FedPulse({ apiKey: 'fp_live_…' });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Never hard-code keys in source — use environment variables.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Resources
|
|
72
|
+
|
|
73
|
+
### `client.opportunities`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// List / search opportunities
|
|
77
|
+
await client.opportunities.list({ keyword: 'cloud', naics: ['541512'] });
|
|
78
|
+
|
|
79
|
+
// Retrieve a single opportunity
|
|
80
|
+
await client.opportunities.get('abc123-notice-id');
|
|
81
|
+
|
|
82
|
+
// Aggregate statistics
|
|
83
|
+
await client.opportunities.stats({ postedAfter: '2025-01-01' });
|
|
84
|
+
|
|
85
|
+
// Request a data export (async job)
|
|
86
|
+
await client.opportunities.createExport({ format: 'csv', filter: { keyword: 'AI' } });
|
|
87
|
+
|
|
88
|
+
// Async generator — walks all pages automatically
|
|
89
|
+
for await (const page of client.opportunities.paginate({ keyword: 'defense' })) {
|
|
90
|
+
console.log(page.data);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `client.exclusions`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// List all current exclusions
|
|
98
|
+
await client.exclusions.list({ active: true });
|
|
99
|
+
|
|
100
|
+
// Retrieve one record
|
|
101
|
+
await client.exclusions.get('exclusion-id');
|
|
102
|
+
|
|
103
|
+
// Bulk check up to 100 entities
|
|
104
|
+
await client.exclusions.check({
|
|
105
|
+
entities: [
|
|
106
|
+
{ uei: 'ABC123DEF456' },
|
|
107
|
+
{ cage: '1A2B3' },
|
|
108
|
+
{ name: 'Acme Corp' },
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Stats
|
|
113
|
+
await client.exclusions.stats();
|
|
114
|
+
|
|
115
|
+
// Page through all exclusions
|
|
116
|
+
for await (const page of client.exclusions.paginate()) {
|
|
117
|
+
console.log(page.data);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `client.entities`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// List registered entities
|
|
125
|
+
await client.entities.list({ naics: ['336411'], state: 'VA' });
|
|
126
|
+
|
|
127
|
+
// Entity by UEI
|
|
128
|
+
await client.entities.get('ABCDEF123456');
|
|
129
|
+
|
|
130
|
+
// Contracts awarded to an entity
|
|
131
|
+
await client.entities.opportunities('ABCDEF123456', { limit: 50 });
|
|
132
|
+
|
|
133
|
+
// Exclusion check (never cached)
|
|
134
|
+
await client.entities.exclusionCheck('ABCDEF123456');
|
|
135
|
+
|
|
136
|
+
// Stats
|
|
137
|
+
await client.entities.stats();
|
|
138
|
+
|
|
139
|
+
// All pages
|
|
140
|
+
for await (const page of client.entities.paginate({ state: 'TX' })) {
|
|
141
|
+
console.log(page.data);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `client.intelligence`
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
// 360° entity intelligence profile
|
|
149
|
+
await client.intelligence.entityProfile('ABCDEF123456');
|
|
150
|
+
|
|
151
|
+
// Market analysis for a NAICS code
|
|
152
|
+
await client.intelligence.marketAnalysis('541512');
|
|
153
|
+
|
|
154
|
+
// Bulk compliance check
|
|
155
|
+
await client.intelligence.complianceCheck({
|
|
156
|
+
entities: [{ uei: 'ABCDEF123456' }, { name: 'Bad Actor LLC' }],
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `client.assistance`
|
|
161
|
+
|
|
162
|
+
Federal grants and assistance listings.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
await client.assistance.list({ keyword: 'broadband', agency: 'USDA' });
|
|
166
|
+
await client.assistance.get('listing-id');
|
|
167
|
+
await client.assistance.getByProgram('10.001');
|
|
168
|
+
await client.assistance.stats();
|
|
169
|
+
for await (const page of client.assistance.paginate()) { … }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `client.analytics`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
await client.analytics.overview({ period: '30d' });
|
|
176
|
+
await client.analytics.usageSummary();
|
|
177
|
+
await client.analytics.topKeywords({ limit: 20 });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### `client.webhooks`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// List configured webhooks
|
|
184
|
+
await client.webhooks.list();
|
|
185
|
+
|
|
186
|
+
// Create
|
|
187
|
+
await client.webhooks.create({ url: 'https://example.com/hook', events: ['opportunity.posted'] });
|
|
188
|
+
|
|
189
|
+
// Update / delete
|
|
190
|
+
await client.webhooks.update('wh_id', { active: false });
|
|
191
|
+
await client.webhooks.delete('wh_id');
|
|
192
|
+
|
|
193
|
+
// Deliveries log
|
|
194
|
+
await client.webhooks.listDeliveries('wh_id');
|
|
195
|
+
await client.webhooks.getDelivery('wh_id', 'delivery-id');
|
|
196
|
+
await client.webhooks.redeliver('wh_id', 'delivery-id');
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Webhook signature verification
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { FedPulse } from '@fedpulse/sdk';
|
|
205
|
+
|
|
206
|
+
// In your Express / Fastify handler:
|
|
207
|
+
app.post('/webhooks', (req, res) => {
|
|
208
|
+
const { signatureHeader, timestampHeader } = FedPulse.extractWebhookHeaders(req.headers);
|
|
209
|
+
|
|
210
|
+
let event;
|
|
211
|
+
try {
|
|
212
|
+
event = FedPulse.verifyWebhook({
|
|
213
|
+
rawBody: req.body, // Buffer or string — the raw unparsed body
|
|
214
|
+
signatureHeader,
|
|
215
|
+
timestampHeader,
|
|
216
|
+
secret: process.env.FEDPULSE_WEBHOOK_SECRET!,
|
|
217
|
+
options: { maxAgeSeconds: 300 },
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
return res.status(400).send('Invalid signature');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(event.event, event.data);
|
|
224
|
+
res.sendStatus(200);
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Pagination helpers
|
|
231
|
+
|
|
232
|
+
Every list resource exposes a `paginate()` async generator that walks all pages for you:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const allOpps: OpportunitySummary[] = [];
|
|
236
|
+
|
|
237
|
+
for await (const page of client.opportunities.paginate({ keyword: 'cloud' })) {
|
|
238
|
+
allOpps.push(...page.data);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Error handling
|
|
245
|
+
|
|
246
|
+
All errors extend `FedPulseError`. Import specific classes for precise handling:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import {
|
|
250
|
+
FedPulse,
|
|
251
|
+
AuthenticationError,
|
|
252
|
+
PermissionError,
|
|
253
|
+
NotFoundError,
|
|
254
|
+
ValidationError,
|
|
255
|
+
RateLimitError,
|
|
256
|
+
ServerError,
|
|
257
|
+
NetworkError,
|
|
258
|
+
TimeoutError,
|
|
259
|
+
RetryExhaustedError,
|
|
260
|
+
} from '@fedpulse/sdk';
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
await client.opportunities.get('bad-id');
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (err instanceof NotFoundError) {
|
|
266
|
+
console.log('Not found');
|
|
267
|
+
} else if (err instanceof RateLimitError) {
|
|
268
|
+
console.log(`Rate limited. Retry after: ${err.retryAfter}s`);
|
|
269
|
+
} else if (err instanceof RetryExhaustedError) {
|
|
270
|
+
console.log(`Failed after ${err.attempts} attempts`, err.lastError);
|
|
271
|
+
} else if (err instanceof NetworkError) {
|
|
272
|
+
console.log('Network issue — check internet connection');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| Class | Status | When thrown |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
280
|
+
| `PermissionError` | 403 | Key lacks required permission |
|
|
281
|
+
| `NotFoundError` | 404 | Resource does not exist |
|
|
282
|
+
| `ValidationError` | 400/422 | Bad request parameters |
|
|
283
|
+
| `RateLimitError` | 429 | Too many requests (check `retryAfter`) |
|
|
284
|
+
| `ServerError` | 5xx | FedPulse server error |
|
|
285
|
+
| `NetworkError` | — | DNS failure, ECONNREFUSED, etc. |
|
|
286
|
+
| `TimeoutError` | — | Request exceeded `timeoutMs` |
|
|
287
|
+
| `RetryExhaustedError` | — | All retry attempts failed |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Configuration
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
const client = new FedPulse({
|
|
295
|
+
apiKey: 'fp_live_…', // required
|
|
296
|
+
baseUrl: 'https://api.fedpulse.dev', // optional override
|
|
297
|
+
timeoutMs: 30_000, // per-attempt timeout (default: 30 s)
|
|
298
|
+
maxRetries: 3, // retries for 5xx / network errors (default: 3)
|
|
299
|
+
cacheSize: 256, // LRU cache entries, 0 to disable (default: 256)
|
|
300
|
+
cacheTtlMs: 60_000, // cache TTL in ms (default: 60 s)
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Rate limit info
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const { data } = await client.opportunities.list({ keyword: 'cloud' });
|
|
308
|
+
console.log(client.rateLimit);
|
|
309
|
+
// { limit: 1000, remaining: 987, reset: 1740000000 }
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Clear cache
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
client.clearCache();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## CommonJS usage
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
const { FedPulse } = require('@fedpulse/sdk');
|
|
324
|
+
const client = new FedPulse({ apiKey: process.env.FEDPULSE_API_KEY });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT © [FedPulse](https://fedpulse.dev)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FedPulse SDK — main client class.
|
|
4
|
+
*
|
|
5
|
+
* Instantiate this class with your API key to access all FedPulse
|
|
6
|
+
* data resources and the webhook verification utility.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { FedPulse } from '@fedpulse/sdk';
|
|
11
|
+
*
|
|
12
|
+
* const client = new FedPulse({ apiKey: process.env.FEDPULSE_API_KEY! });
|
|
13
|
+
*
|
|
14
|
+
* // Search contracts
|
|
15
|
+
* const { data } = await client.opportunities.list({ q: 'cloud', naics: '541512' });
|
|
16
|
+
*
|
|
17
|
+
* // Compliance check
|
|
18
|
+
* const { data: status } = await client.exclusions.check({ entities: [{ uei: 'ABCDEF123456' }] });
|
|
19
|
+
*
|
|
20
|
+
* // Verify an incoming webhook
|
|
21
|
+
* const payload = FedPulse.verifyWebhook({ rawBody, signatureHeader, timestampHeader, secret });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.WebhookVerificationError = exports.FedPulse = void 0;
|
|
26
|
+
const http_js_1 = require("./http.cjs");
|
|
27
|
+
const opportunities_js_1 = require("./resources/opportunities.cjs");
|
|
28
|
+
const exclusions_js_1 = require("./resources/exclusions.cjs");
|
|
29
|
+
const entities_js_1 = require("./resources/entities.cjs");
|
|
30
|
+
const intelligence_js_1 = require("./resources/intelligence.cjs");
|
|
31
|
+
const assistance_js_1 = require("./resources/assistance.cjs");
|
|
32
|
+
const analytics_js_1 = require("./resources/analytics.cjs");
|
|
33
|
+
const webhooks_js_1 = require("./resources/webhooks.cjs");
|
|
34
|
+
const webhooks_verify_js_1 = require("./webhooks-verify.cjs");
|
|
35
|
+
Object.defineProperty(exports, "WebhookVerificationError", { enumerable: true, get: function () { return webhooks_verify_js_1.WebhookVerificationError; } });
|
|
36
|
+
// ── Main client class ──────────────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* The FedPulse SDK client.
|
|
39
|
+
*
|
|
40
|
+
* All API interactions go through the resource properties on this class.
|
|
41
|
+
* The static `verifyWebhook` method can be used independently of a client instance.
|
|
42
|
+
*/
|
|
43
|
+
class FedPulse {
|
|
44
|
+
/** Low-level HTTP client (exposed for advanced usage only). */
|
|
45
|
+
http;
|
|
46
|
+
/** Federal contract opportunities (/v1/opportunities). */
|
|
47
|
+
opportunities;
|
|
48
|
+
/** SAM.gov exclusions and bulk compliance checks (/v1/exclusions). */
|
|
49
|
+
exclusions;
|
|
50
|
+
/** SAM.gov registered entities / vendors (/v1/entities). */
|
|
51
|
+
entities;
|
|
52
|
+
/** 360° entity intelligence and market analysis (/v1/intelligence). */
|
|
53
|
+
intelligence;
|
|
54
|
+
/** Federal assistance listings / CFDA programs (/v1/assistance). */
|
|
55
|
+
assistance;
|
|
56
|
+
/** Per-user API usage analytics (/v1/analytics). */
|
|
57
|
+
analytics;
|
|
58
|
+
/** Webhook subscription management (/v1/webhooks). */
|
|
59
|
+
webhooks;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
this.http = new http_js_1.HttpClient(options);
|
|
62
|
+
this.opportunities = new opportunities_js_1.OpportunitiesResource(this.http);
|
|
63
|
+
this.exclusions = new exclusions_js_1.ExclusionsResource(this.http);
|
|
64
|
+
this.entities = new entities_js_1.EntitiesResource(this.http);
|
|
65
|
+
this.intelligence = new intelligence_js_1.IntelligenceResource(this.http);
|
|
66
|
+
this.assistance = new assistance_js_1.AssistanceResource(this.http);
|
|
67
|
+
this.analytics = new analytics_js_1.AnalyticsResource(this.http);
|
|
68
|
+
this.webhooks = new webhooks_js_1.WebhooksResource(this.http);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Most recent rate-limit info observed from API responses.
|
|
72
|
+
*
|
|
73
|
+
* Updated after every API call. Useful for monitoring your rate-limit usage.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* await client.opportunities.list({ limit: 25 });
|
|
78
|
+
* console.log('Remaining requests:', client.rateLimit?.remaining);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
get rateLimit() {
|
|
82
|
+
return this.http.lastRateLimit;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Clear the in-memory response cache.
|
|
86
|
+
*
|
|
87
|
+
* Useful after writes that may invalidate cached GET responses.
|
|
88
|
+
*/
|
|
89
|
+
clearCache() {
|
|
90
|
+
this.http.clearCache();
|
|
91
|
+
}
|
|
92
|
+
// ── Static webhook utilities ───────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Verify an incoming FedPulse webhook delivery.
|
|
95
|
+
*
|
|
96
|
+
* Validates the HMAC-SHA256 signature, checks the timestamp against replay
|
|
97
|
+
* attacks, and returns the parsed payload on success.
|
|
98
|
+
*
|
|
99
|
+
* **IMPORTANT:** Pass the raw request body bytes — do not parse to JSON first.
|
|
100
|
+
*
|
|
101
|
+
* @param input Headers, raw body, and signing secret.
|
|
102
|
+
* @returns Parsed, verified webhook payload.
|
|
103
|
+
* @throws {WebhookVerificationError} If signature/timestamp is invalid.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* // Express.js with `express.raw({ type: 'application/json' })`:
|
|
108
|
+
* const payload = FedPulse.verifyWebhook<{ noticeId: string }>({
|
|
109
|
+
* rawBody: req.body, // Buffer from express.raw()
|
|
110
|
+
* signatureHeader: req.headers['x-fedpulse-signature'] as string,
|
|
111
|
+
* timestampHeader: req.headers['x-fedpulse-timestamp'] as string,
|
|
112
|
+
* secret: process.env.FEDPULSE_WEBHOOK_SECRET!,
|
|
113
|
+
* });
|
|
114
|
+
* console.log(payload.event, payload.data.noticeId);
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
static verifyWebhook(input) {
|
|
118
|
+
return (0, webhooks_verify_js_1.verifyWebhook)(input);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Extract FedPulse webhook headers from a request headers object.
|
|
122
|
+
*
|
|
123
|
+
* Handles case-insensitive lookup across Express, Fastify, Next.js, etc.
|
|
124
|
+
*
|
|
125
|
+
* @param headers Headers object (plain object or `Headers` instance).
|
|
126
|
+
* @returns Signature header, timestamp header, event type, and delivery ID.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const { signatureHeader, timestampHeader } = FedPulse.extractWebhookHeaders(req.headers);
|
|
131
|
+
* const payload = FedPulse.verifyWebhook({ rawBody, signatureHeader, timestampHeader, secret });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
static extractWebhookHeaders(headers) {
|
|
135
|
+
return (0, webhooks_verify_js_1.extractWebhookHeaders)(headers);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.FedPulse = FedPulse;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FedPulse SDK error classes.
|
|
4
|
+
*
|
|
5
|
+
* All errors thrown by the SDK extend FedPulseError so callers can use
|
|
6
|
+
* `instanceof FedPulseError` to distinguish SDK errors from other exceptions.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.RetryExhaustedError = exports.TimeoutError = exports.NetworkError = exports.ServerError = exports.RateLimitError = exports.ValidationError = exports.NotFoundError = exports.PermissionError = exports.AuthenticationError = exports.FedPulseError = void 0;
|
|
10
|
+
exports.createApiError = createApiError;
|
|
11
|
+
// ── Base error ─────────────────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Base class for all FedPulse SDK errors.
|
|
14
|
+
*/
|
|
15
|
+
class FedPulseError extends Error {
|
|
16
|
+
/** HTTP status code returned by the API, or 0 for network errors. */
|
|
17
|
+
status;
|
|
18
|
+
/** Machine-readable error code from the API (e.g. NOT_FOUND, RATE_LIMIT_EXCEEDED). */
|
|
19
|
+
code;
|
|
20
|
+
/** Request ID for debugging — correlates with API logs. */
|
|
21
|
+
requestId;
|
|
22
|
+
/** Additional structured details from the API response. */
|
|
23
|
+
details;
|
|
24
|
+
constructor({ message, status, code, requestId, details, }) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'FedPulseError';
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.requestId = requestId;
|
|
30
|
+
this.details = details;
|
|
31
|
+
// Restore prototype chain after `extends Error` in transpiled output.
|
|
32
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.FedPulseError = FedPulseError;
|
|
36
|
+
// ── Specific error subtypes ────────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when the API returns 401 Unauthorized.
|
|
39
|
+
* Usually caused by missing, invalid, or revoked API key / JWT.
|
|
40
|
+
*/
|
|
41
|
+
class AuthenticationError extends FedPulseError {
|
|
42
|
+
constructor(params) {
|
|
43
|
+
super({ ...params, status: 401, code: 'UNAUTHORIZED' });
|
|
44
|
+
this.name = 'AuthenticationError';
|
|
45
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.AuthenticationError = AuthenticationError;
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when the API returns 403 Forbidden.
|
|
51
|
+
* Usually caused by insufficient plan permissions or missing key scope.
|
|
52
|
+
*/
|
|
53
|
+
class PermissionError extends FedPulseError {
|
|
54
|
+
constructor(params) {
|
|
55
|
+
super({ ...params, status: 403, code: params.code ?? 'FORBIDDEN' });
|
|
56
|
+
this.name = 'PermissionError';
|
|
57
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.PermissionError = PermissionError;
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when the API returns 404 Not Found.
|
|
63
|
+
*/
|
|
64
|
+
class NotFoundError extends FedPulseError {
|
|
65
|
+
constructor(params) {
|
|
66
|
+
super({ ...params, status: 404, code: 'NOT_FOUND' });
|
|
67
|
+
this.name = 'NotFoundError';
|
|
68
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.NotFoundError = NotFoundError;
|
|
72
|
+
/**
|
|
73
|
+
* Thrown when the API returns 422 Unprocessable Entity or 400 Bad Request
|
|
74
|
+
* due to invalid request parameters or body.
|
|
75
|
+
*/
|
|
76
|
+
class ValidationError extends FedPulseError {
|
|
77
|
+
constructor(params) {
|
|
78
|
+
super({ ...params, status: params.status ?? 422, code: 'VALIDATION_ERROR' });
|
|
79
|
+
this.name = 'ValidationError';
|
|
80
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.ValidationError = ValidationError;
|
|
84
|
+
/**
|
|
85
|
+
* Thrown when the API returns 429 Too Many Requests.
|
|
86
|
+
* The `retryAfter` property indicates when the rate limit resets (Unix seconds).
|
|
87
|
+
*/
|
|
88
|
+
class RateLimitError extends FedPulseError {
|
|
89
|
+
/** Unix epoch seconds when the rate limit window resets. */
|
|
90
|
+
retryAfter;
|
|
91
|
+
constructor(params) {
|
|
92
|
+
super({ ...params, status: 429, code: 'RATE_LIMIT_EXCEEDED' });
|
|
93
|
+
this.name = 'RateLimitError';
|
|
94
|
+
this.retryAfter = params.retryAfter;
|
|
95
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.RateLimitError = RateLimitError;
|
|
99
|
+
/**
|
|
100
|
+
* Thrown when the API returns a 5xx server error.
|
|
101
|
+
*/
|
|
102
|
+
class ServerError extends FedPulseError {
|
|
103
|
+
constructor(params) {
|
|
104
|
+
super({ ...params, status: params.status ?? 500, code: params.code ?? 'INTERNAL_ERROR' });
|
|
105
|
+
this.name = 'ServerError';
|
|
106
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.ServerError = ServerError;
|
|
110
|
+
/**
|
|
111
|
+
* Thrown when a network-level failure occurs (DNS, TCP, timeout, ECONNREFUSED).
|
|
112
|
+
* The `cause` property contains the original error.
|
|
113
|
+
*/
|
|
114
|
+
class NetworkError extends FedPulseError {
|
|
115
|
+
/** The underlying network error. */
|
|
116
|
+
cause;
|
|
117
|
+
constructor(params) {
|
|
118
|
+
super({ ...params, status: 0, code: 'NETWORK_ERROR' });
|
|
119
|
+
this.name = 'NetworkError';
|
|
120
|
+
this.cause = params.cause;
|
|
121
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.NetworkError = NetworkError;
|
|
125
|
+
/**
|
|
126
|
+
* Thrown when a request exceeds the configured timeout.
|
|
127
|
+
*/
|
|
128
|
+
class TimeoutError extends FedPulseError {
|
|
129
|
+
constructor(params) {
|
|
130
|
+
super({ ...params, status: 0, code: 'REQUEST_TIMEOUT' });
|
|
131
|
+
this.name = 'TimeoutError';
|
|
132
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.TimeoutError = TimeoutError;
|
|
136
|
+
/**
|
|
137
|
+
* Thrown when the SDK's retry budget is exhausted.
|
|
138
|
+
* The `lastError` property contains the final underlying error.
|
|
139
|
+
*/
|
|
140
|
+
class RetryExhaustedError extends FedPulseError {
|
|
141
|
+
lastError;
|
|
142
|
+
attempts;
|
|
143
|
+
constructor(params) {
|
|
144
|
+
super({
|
|
145
|
+
message: `Request failed after ${params.attempts} attempt(s): ${params.lastError.message}`,
|
|
146
|
+
status: params.lastError.status,
|
|
147
|
+
code: 'RETRY_EXHAUSTED',
|
|
148
|
+
requestId: params.lastError.requestId,
|
|
149
|
+
});
|
|
150
|
+
this.name = 'RetryExhaustedError';
|
|
151
|
+
this.lastError = params.lastError;
|
|
152
|
+
this.attempts = params.attempts;
|
|
153
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.RetryExhaustedError = RetryExhaustedError;
|
|
157
|
+
/**
|
|
158
|
+
* Factory — parses a raw API error response object into the correct error subtype.
|
|
159
|
+
*
|
|
160
|
+
* @param status HTTP response status code.
|
|
161
|
+
* @param body Parsed JSON body (may be the error envelope or anything).
|
|
162
|
+
* @param headers Response headers map (used for Retry-After).
|
|
163
|
+
*/
|
|
164
|
+
function createApiError(status, body, headers) {
|
|
165
|
+
// Extract standardised error fields from the envelope.
|
|
166
|
+
const envelope = body != null && typeof body === 'object' ? body : {};
|
|
167
|
+
const errObj = envelope['error'] ?? {};
|
|
168
|
+
const message = typeof errObj['message'] === 'string'
|
|
169
|
+
? errObj['message']
|
|
170
|
+
: typeof envelope['message'] === 'string'
|
|
171
|
+
? envelope['message']
|
|
172
|
+
: `HTTP ${status}`;
|
|
173
|
+
const code = typeof errObj['code'] === 'string' ? errObj['code'] : undefined;
|
|
174
|
+
const requestId = typeof envelope['meta']?.['requestId'] === 'string'
|
|
175
|
+
? envelope['meta']['requestId']
|
|
176
|
+
: undefined;
|
|
177
|
+
const details = errObj['details'] ?? undefined;
|
|
178
|
+
switch (status) {
|
|
179
|
+
case 400:
|
|
180
|
+
return new ValidationError({ message, status: 400, requestId, details });
|
|
181
|
+
case 401:
|
|
182
|
+
return new AuthenticationError({ message, requestId, details });
|
|
183
|
+
case 403:
|
|
184
|
+
return new PermissionError({ message, code, requestId, details });
|
|
185
|
+
case 404:
|
|
186
|
+
return new NotFoundError({ message, requestId, details });
|
|
187
|
+
case 422:
|
|
188
|
+
return new ValidationError({ message, status: 422, requestId, details });
|
|
189
|
+
case 429: {
|
|
190
|
+
const retryAfterHeader = headers['retry-after'] ?? headers['x-ratelimit-reset'];
|
|
191
|
+
const retryAfter = retryAfterHeader != null ? Number(retryAfterHeader) : undefined;
|
|
192
|
+
return new RateLimitError({ message, retryAfter: Number.isFinite(retryAfter) ? retryAfter : undefined, requestId, details });
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
if (status >= 500) {
|
|
196
|
+
return new ServerError({ message, status, code, requestId, details });
|
|
197
|
+
}
|
|
198
|
+
return new FedPulseError({ message, status, code: code ?? 'UNKNOWN_ERROR', requestId, details });
|
|
199
|
+
}
|
|
200
|
+
}
|