oilpriceapi 0.7.0 → 0.8.2

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.
Files changed (60) hide show
  1. package/README.md +43 -11
  2. package/dist/cjs/client.js +490 -0
  3. package/dist/cjs/errors.js +80 -0
  4. package/dist/cjs/index.js +82 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/resources/alerts.js +387 -0
  7. package/dist/cjs/resources/analytics.js +226 -0
  8. package/dist/cjs/resources/bunker-fuels.js +196 -0
  9. package/dist/cjs/resources/commodities.js +115 -0
  10. package/dist/cjs/resources/data-quality.js +144 -0
  11. package/dist/cjs/resources/data-sources.js +297 -0
  12. package/dist/cjs/resources/diesel.js +119 -0
  13. package/dist/cjs/resources/drilling.js +269 -0
  14. package/dist/cjs/resources/ei/drilling-productivity.js +108 -0
  15. package/dist/cjs/resources/ei/forecasts.js +106 -0
  16. package/dist/cjs/resources/ei/frac-focus.js +155 -0
  17. package/dist/cjs/resources/ei/index.js +98 -0
  18. package/dist/cjs/resources/ei/oil-inventories.js +97 -0
  19. package/dist/cjs/resources/ei/opec-production.js +97 -0
  20. package/dist/cjs/resources/ei/rig-counts.js +93 -0
  21. package/dist/cjs/resources/ei/well-permits.js +124 -0
  22. package/dist/cjs/resources/forecasts.js +162 -0
  23. package/dist/cjs/resources/futures.js +233 -0
  24. package/dist/cjs/resources/rig-counts.js +161 -0
  25. package/dist/cjs/resources/storage.js +166 -0
  26. package/dist/cjs/resources/webhooks.js +294 -0
  27. package/dist/cjs/types.js +2 -0
  28. package/dist/cjs/version.js +24 -0
  29. package/dist/client.d.ts +33 -2
  30. package/dist/client.js +70 -14
  31. package/dist/errors.d.ts +6 -0
  32. package/dist/errors.js +25 -16
  33. package/dist/index.d.ts +16 -2
  34. package/dist/index.js +24 -1
  35. package/dist/resources/alerts.js +31 -77
  36. package/dist/resources/analytics.js +8 -7
  37. package/dist/resources/bunker-fuels.js +5 -4
  38. package/dist/resources/commodities.js +2 -1
  39. package/dist/resources/data-quality.js +2 -1
  40. package/dist/resources/data-sources.js +21 -77
  41. package/dist/resources/diesel.d.ts +1 -1
  42. package/dist/resources/diesel.js +9 -38
  43. package/dist/resources/drilling.js +2 -1
  44. package/dist/resources/ei/drilling-productivity.js +2 -1
  45. package/dist/resources/ei/forecasts.js +2 -1
  46. package/dist/resources/ei/frac-focus.js +4 -3
  47. package/dist/resources/ei/index.js +2 -1
  48. package/dist/resources/ei/oil-inventories.js +2 -1
  49. package/dist/resources/ei/opec-production.js +2 -1
  50. package/dist/resources/ei/rig-counts.js +2 -1
  51. package/dist/resources/ei/well-permits.js +2 -1
  52. package/dist/resources/forecasts.js +2 -1
  53. package/dist/resources/futures.js +9 -8
  54. package/dist/resources/storage.js +2 -1
  55. package/dist/resources/webhooks.d.ts +36 -0
  56. package/dist/resources/webhooks.js +60 -67
  57. package/dist/types.d.ts +2 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +2 -2
  60. package/package.json +15 -6
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Manage custom data source integrations for Bring Your Own Source (BYOS) feature.
5
5
  */
6
+ import { ValidationError } from "../errors.js";
6
7
  /**
7
8
  * Data Sources Resource
8
9
  *
@@ -86,7 +87,7 @@ export class DataSourcesResource {
86
87
  */
87
88
  async get(id) {
88
89
  if (!id || typeof id !== "string") {
89
- throw new Error("Data source ID must be a non-empty string");
90
+ throw new ValidationError("Data source ID must be a non-empty string");
90
91
  }
91
92
  const response = await this.client["request"](`/v1/data-sources/${id}`, {});
92
93
  return "data_source" in response ? response.data_source : response;
@@ -117,22 +118,17 @@ export class DataSourcesResource {
117
118
  */
118
119
  async create(params) {
119
120
  if (!params.name || typeof params.name !== "string") {
120
- throw new Error("Data source name is required");
121
+ throw new ValidationError("Data source name is required");
121
122
  }
122
123
  if (!params.type) {
123
- throw new Error("Data source type is required");
124
+ throw new ValidationError("Data source type is required");
124
125
  }
125
126
  if (!params.config || typeof params.config !== "object") {
126
- throw new Error("Data source config is required");
127
+ throw new ValidationError("Data source config is required");
127
128
  }
128
- const url = `${this.client["baseUrl"]}/v1/data-sources`;
129
- const response = await fetch(url, {
129
+ const response = await this.client["request"]("/v1/data-sources", {}, {
130
130
  method: "POST",
131
- headers: {
132
- Authorization: `Bearer ${this.client["apiKey"]}`,
133
- "Content-Type": "application/json",
134
- },
135
- body: JSON.stringify({
131
+ body: {
136
132
  data_source: {
137
133
  name: params.name,
138
134
  type: params.type,
@@ -141,14 +137,9 @@ export class DataSourcesResource {
141
137
  sync_frequency_minutes: params.sync_frequency_minutes ?? 60,
142
138
  metadata: params.metadata,
143
139
  },
144
- }),
140
+ },
145
141
  });
146
- if (!response.ok) {
147
- const errorText = await response.text();
148
- throw new Error(`Failed to create data source: ${response.status} ${errorText}`);
149
- }
150
- const data = (await response.json());
151
- return "data_source" in data ? data.data_source : data;
142
+ return "data_source" in response ? response.data_source : response;
152
143
  }
153
144
  /**
154
145
  * Update a data source
@@ -173,25 +164,13 @@ export class DataSourcesResource {
173
164
  */
174
165
  async update(id, params) {
175
166
  if (!id || typeof id !== "string") {
176
- throw new Error("Data source ID must be a non-empty string");
167
+ throw new ValidationError("Data source ID must be a non-empty string");
177
168
  }
178
- const url = `${this.client["baseUrl"]}/v1/data-sources/${id}`;
179
- const response = await fetch(url, {
169
+ const response = await this.client["request"](`/v1/data-sources/${id}`, {}, {
180
170
  method: "PATCH",
181
- headers: {
182
- Authorization: `Bearer ${this.client["apiKey"]}`,
183
- "Content-Type": "application/json",
184
- },
185
- body: JSON.stringify({
186
- data_source: params,
187
- }),
171
+ body: { data_source: params },
188
172
  });
189
- if (!response.ok) {
190
- const errorText = await response.text();
191
- throw new Error(`Failed to update data source: ${response.status} ${errorText}`);
192
- }
193
- const data = (await response.json());
194
- return "data_source" in data ? data.data_source : data;
173
+ return "data_source" in response ? response.data_source : response;
195
174
  }
196
175
  /**
197
176
  * Delete a data source
@@ -209,20 +188,9 @@ export class DataSourcesResource {
209
188
  */
210
189
  async delete(id) {
211
190
  if (!id || typeof id !== "string") {
212
- throw new Error("Data source ID must be a non-empty string");
213
- }
214
- const url = `${this.client["baseUrl"]}/v1/data-sources/${id}`;
215
- const response = await fetch(url, {
216
- method: "DELETE",
217
- headers: {
218
- Authorization: `Bearer ${this.client["apiKey"]}`,
219
- "Content-Type": "application/json",
220
- },
221
- });
222
- if (!response.ok) {
223
- const errorText = await response.text();
224
- throw new Error(`Failed to delete data source: ${response.status} ${errorText}`);
191
+ throw new ValidationError("Data source ID must be a non-empty string");
225
192
  }
193
+ await this.client["request"](`/v1/data-sources/${id}`, {}, { method: "DELETE" });
226
194
  }
227
195
  /**
228
196
  * Test a data source connection
@@ -245,21 +213,9 @@ export class DataSourcesResource {
245
213
  */
246
214
  async test(id) {
247
215
  if (!id || typeof id !== "string") {
248
- throw new Error("Data source ID must be a non-empty string");
249
- }
250
- const url = `${this.client["baseUrl"]}/v1/data-sources/${id}/test`;
251
- const response = await fetch(url, {
252
- method: "POST",
253
- headers: {
254
- Authorization: `Bearer ${this.client["apiKey"]}`,
255
- "Content-Type": "application/json",
256
- },
257
- });
258
- if (!response.ok) {
259
- const errorText = await response.text();
260
- throw new Error(`Failed to test data source: ${response.status} ${errorText}`);
216
+ throw new ValidationError("Data source ID must be a non-empty string");
261
217
  }
262
- return response.json();
218
+ return this.client["request"](`/v1/data-sources/${id}/test`, {}, { method: "POST" });
263
219
  }
264
220
  /**
265
221
  * Get data source sync logs
@@ -283,7 +239,7 @@ export class DataSourcesResource {
283
239
  */
284
240
  async logs(id) {
285
241
  if (!id || typeof id !== "string") {
286
- throw new Error("Data source ID must be a non-empty string");
242
+ throw new ValidationError("Data source ID must be a non-empty string");
287
243
  }
288
244
  const response = await this.client["request"](`/v1/data-sources/${id}/logs`, {});
289
245
  return Array.isArray(response) ? response : response.logs;
@@ -307,7 +263,7 @@ export class DataSourcesResource {
307
263
  */
308
264
  async health(id) {
309
265
  if (!id || typeof id !== "string") {
310
- throw new Error("Data source ID must be a non-empty string");
266
+ throw new ValidationError("Data source ID must be a non-empty string");
311
267
  }
312
268
  return this.client["request"](`/v1/data-sources/${id}/health`, {});
313
269
  }
@@ -330,20 +286,8 @@ export class DataSourcesResource {
330
286
  */
331
287
  async rotateCredentials(id) {
332
288
  if (!id || typeof id !== "string") {
333
- throw new Error("Data source ID must be a non-empty string");
334
- }
335
- const url = `${this.client["baseUrl"]}/v1/data-sources/${id}/rotate-credentials`;
336
- const response = await fetch(url, {
337
- method: "POST",
338
- headers: {
339
- Authorization: `Bearer ${this.client["apiKey"]}`,
340
- "Content-Type": "application/json",
341
- },
342
- });
343
- if (!response.ok) {
344
- const errorText = await response.text();
345
- throw new Error(`Failed to rotate credentials: ${response.status} ${errorText}`);
289
+ throw new ValidationError("Data source ID must be a non-empty string");
346
290
  }
347
- return response.json();
291
+ return this.client["request"](`/v1/data-sources/${id}/rotate-credentials`, {}, { method: "POST" });
348
292
  }
349
293
  }
@@ -1,4 +1,4 @@
1
- import type { OilPriceAPI } from '../client.js';
1
+ import type { OilPriceAPI } from "../client.js";
2
2
  /**
3
3
  * Diesel price data for a specific state or region
4
4
  */
@@ -1,3 +1,4 @@
1
+ import { ValidationError } from "../errors.js";
1
2
  /**
2
3
  * Diesel Prices resource
3
4
  *
@@ -50,9 +51,9 @@ export class DieselResource {
50
51
  */
51
52
  async getPrice(state) {
52
53
  if (!state || state.length !== 2) {
53
- throw new Error('State must be a 2-letter US state code (e.g., "CA", "TX")');
54
+ throw new ValidationError('State must be a 2-letter US state code (e.g., "CA", "TX")');
54
55
  }
55
- const response = await this.client['request']('/v1/diesel-prices', { state: state.toUpperCase() });
56
+ const response = await this.client["request"]("/v1/diesel-prices", { state: state.toUpperCase() });
56
57
  return response.regional_average;
57
58
  }
58
59
  /**
@@ -98,47 +99,17 @@ export class DieselResource {
98
99
  const { lat, lng, radius = 8047 } = options;
99
100
  // Validate coordinates
100
101
  if (lat < -90 || lat > 90) {
101
- throw new Error('Latitude must be between -90 and 90');
102
+ throw new ValidationError("Latitude must be between -90 and 90");
102
103
  }
103
104
  if (lng < -180 || lng > 180) {
104
- throw new Error('Longitude must be between -180 and 180');
105
+ throw new ValidationError("Longitude must be between -180 and 180");
105
106
  }
106
107
  if (radius < 0 || radius > 50000) {
107
- throw new Error('Radius must be between 0 and 50000 meters');
108
+ throw new ValidationError("Radius must be between 0 and 50000 meters");
108
109
  }
109
- // Use POST request for stations endpoint
110
- const response = await fetch(`${this.client['baseUrl']}/v1/diesel-prices/stations`, {
111
- method: 'POST',
112
- headers: {
113
- 'Authorization': `Bearer ${this.client['apiKey']}`,
114
- 'Content-Type': 'application/json',
115
- 'User-Agent': 'oilpriceapi-node/0.4.0',
116
- 'X-SDK-Language': 'javascript',
117
- 'X-SDK-Version': '0.4.0',
118
- 'X-Client-Type': 'sdk',
119
- },
120
- body: JSON.stringify({ lat, lng, radius }),
110
+ return this.client["request"]("/v1/diesel-prices/stations", {}, {
111
+ method: "POST",
112
+ body: { lat, lng, radius },
121
113
  });
122
- if (!response.ok) {
123
- const errorBody = await response.text();
124
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
125
- try {
126
- const errorJson = JSON.parse(errorBody);
127
- errorMessage = errorJson.message || errorJson.error || errorMessage;
128
- }
129
- catch {
130
- // Use default error message
131
- }
132
- // Handle specific status codes
133
- if (response.status === 403) {
134
- throw new Error(`Diesel station queries not available on your plan. ${errorMessage}`);
135
- }
136
- if (response.status === 429) {
137
- throw new Error(`Diesel station query limit exceeded. ${errorMessage}`);
138
- }
139
- throw new Error(errorMessage);
140
- }
141
- const data = await response.json();
142
- return data;
143
114
  }
144
115
  }
@@ -4,6 +4,7 @@
4
4
  * Access US onshore drilling activity data including rig counts, well permits,
5
5
  * frac spreads, DUC wells, completions, and production trends by basin.
6
6
  */
7
+ import { ValidationError } from "../errors.js";
7
8
  /**
8
9
  * Drilling Intelligence Resource
9
10
  *
@@ -257,7 +258,7 @@ export class DrillingIntelligenceResource {
257
258
  */
258
259
  async basin(name) {
259
260
  if (!name || typeof name !== "string") {
260
- throw new Error("Basin name must be a non-empty string");
261
+ throw new ValidationError("Basin name must be a non-empty string");
261
262
  }
262
263
  return this.client["request"](`/v1/drilling-intelligence/basin/${name}`, {});
263
264
  }
@@ -4,6 +4,7 @@
4
4
  * Access EIA Drilling Productivity Report data including new well production,
5
5
  * legacy decline rates, and DUC well inventories by basin.
6
6
  */
7
+ import { ValidationError } from "../../errors.js";
7
8
  /**
8
9
  * EI Drilling Productivity Resource
9
10
  *
@@ -44,7 +45,7 @@ export class EIDrillingProductivityResource {
44
45
  */
45
46
  async get(id) {
46
47
  if (!id || typeof id !== "string") {
47
- throw new Error("Record ID must be a non-empty string");
48
+ throw new ValidationError("Record ID must be a non-empty string");
48
49
  }
49
50
  return this.client["request"](`/v1/ei/drilling_productivities/${id}`, {});
50
51
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Access EIA and IEA price and production forecasts with historical accuracy metrics.
5
5
  */
6
+ import { ValidationError } from "../../errors.js";
6
7
  /**
7
8
  * EI Forecasts Resource
8
9
  *
@@ -42,7 +43,7 @@ export class EIForecastsResource {
42
43
  */
43
44
  async get(id) {
44
45
  if (!id || typeof id !== "string") {
45
- throw new Error("Record ID must be a non-empty string");
46
+ throw new ValidationError("Record ID must be a non-empty string");
46
47
  }
47
48
  return this.client["request"](`/v1/ei/forecasts/${id}`, {});
48
49
  }
@@ -4,6 +4,7 @@
4
4
  * Access hydraulic fracturing disclosure data including chemicals, operators,
5
5
  * and well-level information from the FracFocus registry.
6
6
  */
7
+ import { ValidationError } from "../../errors.js";
7
8
  /**
8
9
  * EI FracFocus Resource
9
10
  *
@@ -53,7 +54,7 @@ export class EIFracFocusResource {
53
54
  */
54
55
  async get(id) {
55
56
  if (!id || typeof id !== "string") {
56
- throw new Error("Record ID must be a non-empty string");
57
+ throw new ValidationError("Record ID must be a non-empty string");
57
58
  }
58
59
  return this.client["request"](`/v1/ei/frac-focus/${id}`, {});
59
60
  }
@@ -129,7 +130,7 @@ export class EIFracFocusResource {
129
130
  */
130
131
  async chemicals(id) {
131
132
  if (!id || typeof id !== "string") {
132
- throw new Error("Disclosure ID must be a non-empty string");
133
+ throw new ValidationError("Disclosure ID must be a non-empty string");
133
134
  }
134
135
  const response = await this.client["request"](`/v1/ei/frac-focus/${id}/chemicals`, {});
135
136
  return Array.isArray(response) ? response : response.chemicals;
@@ -142,7 +143,7 @@ export class EIFracFocusResource {
142
143
  */
143
144
  async forWell(apiNumber) {
144
145
  if (!apiNumber || typeof apiNumber !== "string") {
145
- throw new Error("API number must be a non-empty string");
146
+ throw new ValidationError("API number must be a non-empty string");
146
147
  }
147
148
  const response = await this.client["request"](`/v1/ei/frac-focus/for-well/${apiNumber}`, {});
148
149
  return Array.isArray(response) ? response : response.data;
@@ -11,6 +11,7 @@ import { EIDrillingProductivityResource } from "./drilling-productivity.js";
11
11
  import { EIForecastsResource } from "./forecasts.js";
12
12
  import { EIWellPermitsResource } from "./well-permits.js";
13
13
  import { EIFracFocusResource } from "./frac-focus.js";
14
+ import { ValidationError } from "../../errors.js";
14
15
  /**
15
16
  * Energy Intelligence Resource
16
17
  *
@@ -79,7 +80,7 @@ export class EnergyIntelligenceResource {
79
80
  */
80
81
  async wellTimeline(apiNumber) {
81
82
  if (!apiNumber || typeof apiNumber !== "string") {
82
- throw new Error("API number must be a non-empty string");
83
+ throw new ValidationError("API number must be a non-empty string");
83
84
  }
84
85
  return this.client["request"](`/v1/ei/wells/${apiNumber}/timeline`, {});
85
86
  }
@@ -4,6 +4,7 @@
4
4
  * Access EIA crude oil inventory data including total US stocks, Cushing levels,
5
5
  * and regional breakdowns.
6
6
  */
7
+ import { ValidationError } from "../../errors.js";
7
8
  /**
8
9
  * EI Oil Inventories Resource
9
10
  *
@@ -43,7 +44,7 @@ export class EIOilInventoriesResource {
43
44
  */
44
45
  async get(id) {
45
46
  if (!id || typeof id !== "string") {
46
- throw new Error("Record ID must be a non-empty string");
47
+ throw new ValidationError("Record ID must be a non-empty string");
47
48
  }
48
49
  return this.client["request"](`/v1/ei/oil_inventories/${id}`, {});
49
50
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Access OPEC crude oil production data by country with historical trends.
5
5
  */
6
+ import { ValidationError } from "../../errors.js";
6
7
  /**
7
8
  * EI OPEC Production Resource
8
9
  *
@@ -42,7 +43,7 @@ export class EIOPECProductionResource {
42
43
  */
43
44
  async get(id) {
44
45
  if (!id || typeof id !== "string") {
45
- throw new Error("Record ID must be a non-empty string");
46
+ throw new ValidationError("Record ID must be a non-empty string");
46
47
  }
47
48
  return this.client["request"](`/v1/ei/opec_productions/${id}`, {});
48
49
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Access Baker Hughes rig count data with basin, state, and historical breakdowns.
5
5
  */
6
+ import { ValidationError } from "../../errors.js";
6
7
  /**
7
8
  * EI Rig Counts Resource
8
9
  *
@@ -46,7 +47,7 @@ export class EIRigCountsResource {
46
47
  */
47
48
  async get(id) {
48
49
  if (!id || typeof id !== "string") {
49
- throw new Error("Record ID must be a non-empty string");
50
+ throw new ValidationError("Record ID must be a non-empty string");
50
51
  }
51
52
  return this.client["request"](`/v1/ei/rig_counts/${id}`, {});
52
53
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Access oil and gas well permit data by state, operator, and formation.
5
5
  */
6
+ import { ValidationError } from "../../errors.js";
6
7
  /**
7
8
  * EI Well Permits Resource
8
9
  *
@@ -48,7 +49,7 @@ export class EIWellPermitsResource {
48
49
  */
49
50
  async get(id) {
50
51
  if (!id || typeof id !== "string") {
51
- throw new Error("Record ID must be a non-empty string");
52
+ throw new ValidationError("Record ID must be a non-empty string");
52
53
  }
53
54
  return this.client["request"](`/v1/ei/well-permits/${id}`, {});
54
55
  }
@@ -4,6 +4,7 @@
4
4
  * Access official price forecasts from EIA, IEA, and other agencies including
5
5
  * monthly outlooks, accuracy metrics, and historical archives.
6
6
  */
7
+ import { ValidationError } from "../errors.js";
7
8
  /**
8
9
  * Forecasts Resource
9
10
  *
@@ -147,7 +148,7 @@ export class ForecastsResource {
147
148
  */
148
149
  async get(period, commodity) {
149
150
  if (!period || typeof period !== "string") {
150
- throw new Error("Period must be a non-empty string");
151
+ throw new ValidationError("Period must be a non-empty string");
151
152
  }
152
153
  const params = {};
153
154
  if (commodity)
@@ -4,6 +4,7 @@
4
4
  * Access futures contract data including latest prices, historical data,
5
5
  * OHLC, intraday, spreads, curves, and continuous contracts.
6
6
  */
7
+ import { ValidationError } from "../errors.js";
7
8
  /**
8
9
  * Futures Resource
9
10
  *
@@ -52,7 +53,7 @@ export class FuturesResource {
52
53
  */
53
54
  async latest(contract) {
54
55
  if (!contract || typeof contract !== "string") {
55
- throw new Error("Contract symbol must be a non-empty string");
56
+ throw new ValidationError("Contract symbol must be a non-empty string");
56
57
  }
57
58
  return this.client["request"](`/v1/futures/${contract}`, {});
58
59
  }
@@ -77,7 +78,7 @@ export class FuturesResource {
77
78
  */
78
79
  async historical(contract, options) {
79
80
  if (!contract || typeof contract !== "string") {
80
- throw new Error("Contract symbol must be a non-empty string");
81
+ throw new ValidationError("Contract symbol must be a non-empty string");
81
82
  }
82
83
  const params = {};
83
84
  if (options?.startDate)
@@ -108,7 +109,7 @@ export class FuturesResource {
108
109
  */
109
110
  async ohlc(contract, date) {
110
111
  if (!contract || typeof contract !== "string") {
111
- throw new Error("Contract symbol must be a non-empty string");
112
+ throw new ValidationError("Contract symbol must be a non-empty string");
112
113
  }
113
114
  const params = {};
114
115
  if (date)
@@ -136,7 +137,7 @@ export class FuturesResource {
136
137
  */
137
138
  async intraday(contract) {
138
139
  if (!contract || typeof contract !== "string") {
139
- throw new Error("Contract symbol must be a non-empty string");
140
+ throw new ValidationError("Contract symbol must be a non-empty string");
140
141
  }
141
142
  return this.client["request"](`/v1/futures/${contract}/intraday`, {});
142
143
  }
@@ -161,10 +162,10 @@ export class FuturesResource {
161
162
  */
162
163
  async spreads(contract1, contract2) {
163
164
  if (!contract1 || typeof contract1 !== "string") {
164
- throw new Error("First contract symbol must be a non-empty string");
165
+ throw new ValidationError("First contract symbol must be a non-empty string");
165
166
  }
166
167
  if (!contract2 || typeof contract2 !== "string") {
167
- throw new Error("Second contract symbol must be a non-empty string");
168
+ throw new ValidationError("Second contract symbol must be a non-empty string");
168
169
  }
169
170
  return this.client["request"]("/v1/futures/spreads", {
170
171
  contract1,
@@ -193,7 +194,7 @@ export class FuturesResource {
193
194
  */
194
195
  async curve(contract) {
195
196
  if (!contract || typeof contract !== "string") {
196
- throw new Error("Contract symbol must be a non-empty string");
197
+ throw new ValidationError("Contract symbol must be a non-empty string");
197
198
  }
198
199
  return this.client["request"](`/v1/futures/${contract}/curve`, {});
199
200
  }
@@ -218,7 +219,7 @@ export class FuturesResource {
218
219
  */
219
220
  async continuous(contract, months) {
220
221
  if (!contract || typeof contract !== "string") {
221
- throw new Error("Contract symbol must be a non-empty string");
222
+ throw new ValidationError("Contract symbol must be a non-empty string");
222
223
  }
223
224
  const params = {};
224
225
  if (months !== undefined)
@@ -4,6 +4,7 @@
4
4
  * Access crude oil storage data including total US inventory, Cushing hub levels,
5
5
  * Strategic Petroleum Reserve (SPR), and regional breakdowns.
6
6
  */
7
+ import { ValidationError } from "../errors.js";
7
8
  /**
8
9
  * Storage Resource
9
10
  *
@@ -148,7 +149,7 @@ export class StorageResource {
148
149
  */
149
150
  async history(code, options) {
150
151
  if (!code || typeof code !== "string") {
151
- throw new Error("Storage location code must be a non-empty string");
152
+ throw new ValidationError("Storage location code must be a non-empty string");
152
153
  }
153
154
  const params = {};
154
155
  if (options?.startDate)
@@ -287,4 +287,40 @@ export declare class WebhooksResource {
287
287
  * ```
288
288
  */
289
289
  events(id: string): Promise<WebhookEvent[]>;
290
+ /**
291
+ * Verify a webhook signature.
292
+ *
293
+ * Validates that a webhook payload was sent by OilPriceAPI by checking
294
+ * the HMAC-SHA256 signature. Uses constant-time comparison to prevent
295
+ * timing attacks.
296
+ *
297
+ * @param payload - Raw request body (string or Buffer)
298
+ * @param signature - Value of the X-OilPriceAPI-Signature header (e.g., "sha256=abc123...")
299
+ * @param secret - Your webhook signing secret
300
+ * @returns true if signature is valid
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * import express from 'express';
305
+ * import { OilPriceAPI } from 'oilpriceapi';
306
+ *
307
+ * const app = express();
308
+ * const client = new OilPriceAPI({ apiKey: 'your_key' });
309
+ *
310
+ * // Use raw body parser for webhook routes
311
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
312
+ * const signature = req.headers['x-oilpriceapi-signature'] as string;
313
+ * const isValid = client.webhooks.verifySignature(req.body, signature, 'your_secret');
314
+ *
315
+ * if (!isValid) {
316
+ * return res.status(401).send('Invalid signature');
317
+ * }
318
+ *
319
+ * const event = JSON.parse(req.body.toString());
320
+ * console.log('Verified webhook:', event.type);
321
+ * res.sendStatus(200);
322
+ * });
323
+ * ```
324
+ */
325
+ verifySignature(payload: string | Buffer, signature: string, secret: string): boolean;
290
326
  }