minifetch-api 1.6.0 → 1.7.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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </a>
6
6
  </div>
7
7
 
8
- **[Minifetch](https://minifetch.com) is a hosted toolkit of web page extraction primitives.** Run them as a full technical SEO audit or call one at a time for a fraction of the price — and a fraction of the LLM tokens. No subscription.
8
+ **[Minifetch](https://minifetch.com) is a hosted toolkit for web developers and AI agents. Search, scrape, extract & SEO audit web pages.** Pay per fetch, no subscription.
9
9
 
10
10
  - ✅ **Always pay-per-fetch at competitive prices.**
11
11
  - ✅ [Sign up](https://minifetch.com/dashboard) for an account & get free starter credits. 🎉🎉
@@ -72,19 +72,15 @@ After the Quick Start, you have the following methods to use.
72
72
 
73
73
  **Wrap** these methods in a **try/catch** just like in the Quick Start example above. **Code examples** can be also found in the [Github repository /example- directories](https://github.com/Niche-Networks/minifetch-api/).
74
74
 
75
- The `checkAndExtract` methods check the target URL's `robots.txt` file to ensure its not blocked and tell us your preferred crawl delay (defaults to 1 second between requests to your domain). So fetching 10 URLs takes at least 10 seconds to complete by default. This is by design, so Minifetch never hammers your server or slows it down for your real users. If you own the site and want to allow Minifetch access or to set custom rules for it, read [How To Unblock Minifetch](https://minifetch.com/tutorials/unblock-minifetch).
76
-
77
75
  All API methods default to POST unless you set options to `{ method: 'GET' }` and pass in as the second argument.
78
76
 
77
+ The `checkAndExtract` methods check the target URL's `robots.txt` file to ensure its not blocked and tell us your preferred crawl delay (defaults to 1 second between requests to your domain). So fetching 10 URLs takes at least 10 seconds to complete by default. This is by design, so Minifetch never hammers your server or slows it down for your real users. If you own the site and want to allow Minifetch access or to set custom rules for it, read [How To Unblock Minifetch](https://minifetch.com/tutorials/unblock-minifetch).
78
+
79
79
  ```js
80
- await client.checkAndRunSeoPageAudit(url);
81
- // Price: $0.01
82
- // Runs a full technical SEO audit on your URL. Combines data from
83
- // the other API endpoints and runs checks that each return a PASS/
84
- // WARN/ FAIL result with no black-box scoring. Just deterministic,
85
- // composable signal you can act on or pipe into an agent.
86
- // Audit rules are documented in the skill file:
87
- // https://minifetch.com/skills/seo-page-audit/SKILL.md
80
+ await client.searchByKeyword("green tea");
81
+ // Price: $0.002
82
+ // Keyword web search by Ceramic.ai that returns ranked results as
83
+ // structured JSON: a title, URL, and text snippet per result.
88
84
 
89
85
  await client.checkAndExtractUrlMetadata(url, options);
90
86
  // Price: $0.002
@@ -108,36 +104,45 @@ await client.checkAndExtractUrlLinks(url);
108
104
  // anchor text variants used for each) and top external domains by
109
105
  // link count.
110
106
 
111
- await client.checkAndExtractUrlPreview(url);
112
- // Price: $0.002
113
- // Extracts all fields used for a page's share previews: the lightweight
114
- // cards that represent the page on social platforms, chat apps, and AI.
115
-
116
107
  await client.checkAndExtractUrlContent(url, options);
117
108
  // Price: $0.002
118
109
  // For site owners auditing AI readability: returns the clean markdown
119
110
  // an LLM extracts from your page after nav, ads, & scripts are stripped.
120
111
  // See what survives for AEO and AI search; respects robots.txt.
121
112
  // Options: { includeMediaUrls: true } - defaults to false.
113
+
114
+ await client.checkAndExtractUrlPreview(url);
115
+ // Price: $0.002
116
+ // Extracts all fields used for a page's share previews: the lightweight
117
+ // cards that represent the page on social platforms, chat apps, and AI.
118
+
119
+ await client.checkAndRunSeoPageAudit(url);
120
+ // Price: $0.01
121
+ // Runs a full technical SEO audit on your URL. Combines data from
122
+ // the other API endpoints and runs checks that each return a PASS/
123
+ // WARN/ FAIL result with no black-box scoring. Just deterministic,
124
+ // composable signal you can act on or pipe into an agent.
125
+ // Audit rules are documented in the skill file:
126
+ // https://minifetch.com/skills/seo-page-audit/SKILL.md
122
127
  ```
123
128
 
124
129
  For max control, you can also use the following methods directly:
125
130
  ```js
126
131
  await client.preflightCheck(url, options);
127
- // Free: check if `minfetch` user agent can access target via robots.txt
132
+ // Free: check if `minfetch` user agent can access target URL via robots.txt
128
133
  // Options: { "fresh": true } - bypass 24hr robots.txt cache, defaults to false
129
134
 
130
135
  // Paid methods:
131
- await client.runSeoPageAudit(url);
132
136
  await client.extractUrlMetadata(url, options); // same options as above
133
137
  await client.extractUrlLinks(url);
134
- await client.extractUrlPreview(url);
135
138
  await client.extractUrlContent(url, options); // same options as above
139
+ await client.extractUrlPreview(url);
140
+ await client.runSeoPageAudit(url);
136
141
  ```
137
142
  ---
138
143
 
139
144
  ### Error Types
140
- When you wrap the functions above in a try/catch, here are some of the errors you may encounter. You are never charged for URLs that are blocked or error.
145
+ When you wrap the functions above in a try/catch, here are some of the errors you may encounter. You are never charged for target URLs that are blocked or error.
141
146
 
142
147
  - **"InvalidURLError: Invalid url ${url}"**
143
148
  - The URL is malformed in some way, correct it and try again.
@@ -1,7 +1,7 @@
1
1
  import { initConfig } from "./init.js";
2
- import { validateAndNormalizeUrl } from "./utils/validation.js";
2
+ import { validateAndNormalizeUrl, validateAndNormalizeQuery } from "./utils/validation.js";
3
3
  import { handlePayment, handleApiKeyRequest } from "./utils/payment.js";
4
- import { InvalidUrlError, RobotsBlockedError, PaymentFailedError, ExtractionFailedError, NetworkError, ConfigurationError, } from "./types/errors.js";
4
+ import { InvalidUrlError, InvalidQueryError, RobotsBlockedError, PaymentFailedError, ExtractionFailedError, SearchFailedError, NetworkError, ConfigurationError, } from "./types/errors.js";
5
5
  /** Every endpoint accepts GET (query string) or POST (JSON body). POST is the default. */
6
6
  const DEFAULT_METHOD = "POST";
7
7
  /**
@@ -24,6 +24,39 @@ export class MinifetchClient {
24
24
  this.config = initConfig(config);
25
25
  this.baseUrl = this.config.apiBaseUrl;
26
26
  }
27
+ /**
28
+ * Search the web by keyword (paid endpoint).
29
+ *
30
+ * Unlike the URL-based methods, this takes a search query rather than a URL and
31
+ * proxies to Minifetch's keyword search. Returns ranked results, each with a
32
+ * title, URL, and text snippet. `limit` and `descriptionLength` are convenience
33
+ * knobs the server clamps into range; the effective values (after clamping) are
34
+ * echoed back on `queryParameters`.
35
+ *
36
+ * @param query - keyword(s) to search for (1-50 characters)
37
+ * @param options
38
+ * @param options.limit - number of results, 1-10 (default 10). Out-of-range values are clamped.
39
+ * @param options.descriptionLength - max characters per snippet, 0-5000 (default 750, 0 = titles/URLs only). Out-of-range values are clamped.
40
+ * @param options.method - "GET" or "POST" (default POST)
41
+ * @throws {InvalidQueryError} if the query is empty or exceeds 50 characters
42
+ * @throws {SearchFailedError} if the search request fails
43
+ * @throws {PaymentFailedError} if x402 payment fails
44
+ * @throws {NetworkError} various reasons, check README
45
+ */
46
+ async searchByKeyword(query, options) {
47
+ try {
48
+ const cleanQuery = validateAndNormalizeQuery(query);
49
+ const params = { query: cleanQuery };
50
+ if (options?.limit !== undefined)
51
+ params.limit = options.limit;
52
+ if (options?.descriptionLength !== undefined)
53
+ params.descriptionLength = options.descriptionLength;
54
+ return await this._makeSearchRequest(params, options?.method);
55
+ }
56
+ catch (error) {
57
+ return this._rethrowSearchError(error, query, "Keyword search");
58
+ }
59
+ }
27
60
  /**
28
61
  * Check if URL is allowed by robots.txt (free preflight check — no auth required)
29
62
  *
@@ -85,27 +118,6 @@ export class MinifetchClient {
85
118
  return this._rethrowError(error, url, "Paid URL check");
86
119
  }
87
120
  }
88
- /**
89
- * Run SEO page audit (paid endpoint)
90
- *
91
- * @param url
92
- * @param options
93
- * @param options.method - "GET" or "POST" (default POST)
94
- * @throws {InvalidUrlError} if URL is invalid
95
- * @throws {ExtractionFailedError} various reasons, check README
96
- * @throws {PaymentFailedError} if x402 payment fails
97
- * @throws {NetworkError} various reasons, check README
98
- */
99
- async runSeoPageAudit(url, options) {
100
- try {
101
- const normalizedUrl = validateAndNormalizeUrl(url);
102
- const params = { url: normalizedUrl };
103
- return await this._makeRequest("/run/seo-page-audit", normalizedUrl, "Run SEO page audit", params, options?.method);
104
- }
105
- catch (error) {
106
- return this._rethrowError(error, url, "Run SEO page audit");
107
- }
108
- }
109
121
  /**
110
122
  * Extract URL metadata (paid endpoint)
111
123
  *
@@ -202,6 +214,27 @@ export class MinifetchClient {
202
214
  return this._rethrowError(error, url, "Content extraction");
203
215
  }
204
216
  }
217
+ /**
218
+ * Run SEO page audit (paid endpoint)
219
+ *
220
+ * @param url
221
+ * @param options
222
+ * @param options.method - "GET" or "POST" (default POST)
223
+ * @throws {InvalidUrlError} if URL is invalid
224
+ * @throws {ExtractionFailedError} various reasons, check README
225
+ * @throws {PaymentFailedError} if x402 payment fails
226
+ * @throws {NetworkError} various reasons, check README
227
+ */
228
+ async runSeoPageAudit(url, options) {
229
+ try {
230
+ const normalizedUrl = validateAndNormalizeUrl(url);
231
+ const params = { url: normalizedUrl };
232
+ return await this._makeRequest("/run/seo-page-audit", normalizedUrl, "Run SEO page audit", params, options?.method);
233
+ }
234
+ catch (error) {
235
+ return this._rethrowError(error, url, "Run SEO page audit");
236
+ }
237
+ }
205
238
  /**
206
239
  * Check URL then run SEO page audit in one call.
207
240
  * Throws RobotsBlockedError if robots.txt blocks the URL.
@@ -336,6 +369,45 @@ export class MinifetchClient {
336
369
  return { success: data.success, results: data.results };
337
370
  }
338
371
  }
372
+ /**
373
+ * Search-specific request path. Mirrors {@link _makeRequest} but for the
374
+ * keyword search endpoint: there is no URL, non-OK responses surface as
375
+ * SearchFailedError (carrying the query), and the server's clamped
376
+ * `queryParameters` echo is preserved rather than dropped.
377
+ *
378
+ * @param params - request params (query + optional limit/descriptionLength)
379
+ * @param method - "GET" or "POST" (default POST)
380
+ */
381
+ async _makeSearchRequest(params, method = DEFAULT_METHOD) {
382
+ const query = String(params.query);
383
+ const { url, init } = this._buildRequest(this._paidPath("/search/keyword"), params, method);
384
+ if (this.config.authMode === "x402") {
385
+ const { response, payment } = await handlePayment(url, this.config, init);
386
+ if (!response.ok) {
387
+ throw new SearchFailedError(query, `Keyword search failed: ${response.status} ${response.statusText}`, response.status);
388
+ }
389
+ const data = (await response.json());
390
+ return {
391
+ success: data.success,
392
+ queryParameters: data.queryParameters,
393
+ results: data.results,
394
+ payment,
395
+ };
396
+ }
397
+ else {
398
+ const { response } = await handleApiKeyRequest(url, this.config, init);
399
+ if (!response.ok) {
400
+ throw new SearchFailedError(query, `Keyword search failed: ${response.status} ${response.statusText}`, response.status);
401
+ }
402
+ const data = (await response.json());
403
+ // payment field intentionally omitted for apiKey auth — not applicable
404
+ return {
405
+ success: data.success,
406
+ queryParameters: data.queryParameters,
407
+ results: data.results,
408
+ };
409
+ }
410
+ }
339
411
  /**
340
412
  * Preflight check helper — throws RobotsBlockedError if not allowed
341
413
  *
@@ -364,4 +436,21 @@ export class MinifetchClient {
364
436
  }
365
437
  throw new ExtractionFailedError(url, `${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
366
438
  }
439
+ /**
440
+ * Search sibling of {@link _rethrowError}: re-throw known search error types,
441
+ * wrapping anything else in SearchFailedError (which carries the query, not a URL).
442
+ *
443
+ * @param error
444
+ * @param query
445
+ * @param label
446
+ */
447
+ _rethrowSearchError(error, query, label) {
448
+ if (error instanceof InvalidQueryError ||
449
+ error instanceof SearchFailedError ||
450
+ error instanceof PaymentFailedError ||
451
+ error instanceof NetworkError) {
452
+ throw error;
453
+ }
454
+ throw new SearchFailedError(query, `${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
455
+ }
367
456
  }
@@ -120,3 +120,47 @@ export class NetworkError extends MinifetchError {
120
120
  Object.setPrototypeOf(this, NetworkError.prototype);
121
121
  }
122
122
  }
123
+ /**
124
+ * Thrown when keyword-search query validation fails (empty, or over 50 chars).
125
+ * The query sibling of {@link InvalidUrlError} — searchByKeyword takes a query,
126
+ * not a URL, so bad input surfaces here instead.
127
+ */
128
+ export class InvalidQueryError extends MinifetchError {
129
+ query;
130
+ /**
131
+ *
132
+ * @param query
133
+ * @param message
134
+ */
135
+ constructor(query, message) {
136
+ super(message || `Invalid query: ${query}`);
137
+ this.name = "InvalidQueryError";
138
+ this.query = query;
139
+ Object.setPrototypeOf(this, InvalidQueryError.prototype);
140
+ }
141
+ }
142
+ /**
143
+ * Thrown when a keyword search fails (non-OK response or unexpected error).
144
+ * The search sibling of {@link ExtractionFailedError}: it carries the `query`
145
+ * rather than a `url`, since search has no target URL.
146
+ */
147
+ export class SearchFailedError extends MinifetchError {
148
+ query;
149
+ statusCode;
150
+ originalError;
151
+ /**
152
+ *
153
+ * @param query
154
+ * @param message
155
+ * @param statusCode
156
+ * @param originalError
157
+ */
158
+ constructor(query, message, statusCode, originalError) {
159
+ super(message);
160
+ this.name = "SearchFailedError";
161
+ this.query = query;
162
+ this.statusCode = statusCode;
163
+ this.originalError = originalError;
164
+ Object.setPrototypeOf(this, SearchFailedError.prototype);
165
+ }
166
+ }
@@ -1,8 +1,13 @@
1
- import { InvalidUrlError } from "../types/errors.js";
1
+ import { InvalidUrlError, InvalidQueryError } from "../types/errors.js";
2
2
  /**
3
3
  * Maximum allowed URL length
4
4
  */
5
5
  const MAX_URL_LENGTH = 2048;
6
+ /**
7
+ * Maximum allowed search-query length. Mirrors Ceramic's hard cap on the server
8
+ * so we fail fast client-side instead of spending a paid call on a 400.
9
+ */
10
+ const MAX_QUERY_LENGTH = 50;
6
11
  /**
7
12
  * Allowed URL protocols
8
13
  */
@@ -77,6 +82,27 @@ export function validateAndNormalizeUrl(url) {
77
82
  }
78
83
  return normalized;
79
84
  }
85
+ /**
86
+ * Validate and normalize a keyword-search query.
87
+ * Mirrors the server: strips null bytes, normalizes unicode, trims, then enforces
88
+ * the same non-empty / 50-char rules so bad input fails before a paid call.
89
+ *
90
+ * @param query
91
+ * @throws {InvalidQueryError} if the query is empty or exceeds 50 characters
92
+ */
93
+ export function validateAndNormalizeQuery(query) {
94
+ if (!query || typeof query !== "string") {
95
+ throw new InvalidQueryError(String(query ?? ""), "Query must be a non-empty string");
96
+ }
97
+ const cleaned = query.replace(/\0/g, "").normalize("NFC").trim();
98
+ if (!cleaned) {
99
+ throw new InvalidQueryError(query, "Query must not be empty");
100
+ }
101
+ if (cleaned.length > MAX_QUERY_LENGTH) {
102
+ throw new InvalidQueryError(cleaned, `Query exceeds maximum length of ${MAX_QUERY_LENGTH} characters`);
103
+ }
104
+ return cleaned;
105
+ }
80
106
  /**
81
107
  * Check if hostname is localhost or private IP
82
108
  *
@@ -1,5 +1,5 @@
1
1
  import type { ClientConfig, HttpMethod } from "./types/config.js";
2
- import type { PreflightCheckResponse, PaidEndpointResponse } from "./types/responses.js";
2
+ import type { PreflightCheckResponse, PaidEndpointResponse, SearchKeywordResponse } from "./types/responses.js";
3
3
  /**
4
4
  * Main Minifetch API client.
5
5
  * Supports two auth modes:
@@ -17,6 +17,30 @@ export declare class MinifetchClient {
17
17
  * @param config - Either { network, privateKey } for x402 or { apiKey } for API key auth
18
18
  */
19
19
  constructor(config: ClientConfig);
20
+ /**
21
+ * Search the web by keyword (paid endpoint).
22
+ *
23
+ * Unlike the URL-based methods, this takes a search query rather than a URL and
24
+ * proxies to Minifetch's keyword search. Returns ranked results, each with a
25
+ * title, URL, and text snippet. `limit` and `descriptionLength` are convenience
26
+ * knobs the server clamps into range; the effective values (after clamping) are
27
+ * echoed back on `queryParameters`.
28
+ *
29
+ * @param query - keyword(s) to search for (1-50 characters)
30
+ * @param options
31
+ * @param options.limit - number of results, 1-10 (default 10). Out-of-range values are clamped.
32
+ * @param options.descriptionLength - max characters per snippet, 0-5000 (default 750, 0 = titles/URLs only). Out-of-range values are clamped.
33
+ * @param options.method - "GET" or "POST" (default POST)
34
+ * @throws {InvalidQueryError} if the query is empty or exceeds 50 characters
35
+ * @throws {SearchFailedError} if the search request fails
36
+ * @throws {PaymentFailedError} if x402 payment fails
37
+ * @throws {NetworkError} various reasons, check README
38
+ */
39
+ searchByKeyword(query: string, options?: {
40
+ limit?: number;
41
+ descriptionLength?: number;
42
+ method?: HttpMethod;
43
+ }): Promise<SearchKeywordResponse>;
20
44
  /**
21
45
  * Check if URL is allowed by robots.txt (free preflight check — no auth required)
22
46
  *
@@ -51,20 +75,6 @@ export declare class MinifetchClient {
51
75
  fresh?: boolean;
52
76
  method?: HttpMethod;
53
77
  }): Promise<PaidEndpointResponse>;
54
- /**
55
- * Run SEO page audit (paid endpoint)
56
- *
57
- * @param url
58
- * @param options
59
- * @param options.method - "GET" or "POST" (default POST)
60
- * @throws {InvalidUrlError} if URL is invalid
61
- * @throws {ExtractionFailedError} various reasons, check README
62
- * @throws {PaymentFailedError} if x402 payment fails
63
- * @throws {NetworkError} various reasons, check README
64
- */
65
- runSeoPageAudit(url: string, options?: {
66
- method?: HttpMethod;
67
- }): Promise<PaidEndpointResponse>;
68
78
  /**
69
79
  * Extract URL metadata (paid endpoint)
70
80
  *
@@ -129,6 +139,20 @@ export declare class MinifetchClient {
129
139
  includeMediaUrls?: boolean;
130
140
  method?: HttpMethod;
131
141
  }): Promise<PaidEndpointResponse>;
142
+ /**
143
+ * Run SEO page audit (paid endpoint)
144
+ *
145
+ * @param url
146
+ * @param options
147
+ * @param options.method - "GET" or "POST" (default POST)
148
+ * @throws {InvalidUrlError} if URL is invalid
149
+ * @throws {ExtractionFailedError} various reasons, check README
150
+ * @throws {PaymentFailedError} if x402 payment fails
151
+ * @throws {NetworkError} various reasons, check README
152
+ */
153
+ runSeoPageAudit(url: string, options?: {
154
+ method?: HttpMethod;
155
+ }): Promise<PaidEndpointResponse>;
132
156
  /**
133
157
  * Check URL then run SEO page audit in one call.
134
158
  * Throws RobotsBlockedError if robots.txt blocks the URL.
@@ -223,6 +247,16 @@ export declare class MinifetchClient {
223
247
  * @param method - "GET" or "POST" (default POST)
224
248
  */
225
249
  private _makeRequest;
250
+ /**
251
+ * Search-specific request path. Mirrors {@link _makeRequest} but for the
252
+ * keyword search endpoint: there is no URL, non-OK responses surface as
253
+ * SearchFailedError (carrying the query), and the server's clamped
254
+ * `queryParameters` echo is preserved rather than dropped.
255
+ *
256
+ * @param params - request params (query + optional limit/descriptionLength)
257
+ * @param method - "GET" or "POST" (default POST)
258
+ */
259
+ private _makeSearchRequest;
226
260
  /**
227
261
  * Preflight check helper — throws RobotsBlockedError if not allowed
228
262
  *
@@ -237,4 +271,13 @@ export declare class MinifetchClient {
237
271
  * @param label
238
272
  */
239
273
  private _rethrowError;
274
+ /**
275
+ * Search sibling of {@link _rethrowError}: re-throw known search error types,
276
+ * wrapping anything else in SearchFailedError (which carries the query, not a URL).
277
+ *
278
+ * @param error
279
+ * @param query
280
+ * @param label
281
+ */
282
+ private _rethrowSearchError;
240
283
  }
@@ -84,3 +84,35 @@ export declare class NetworkError extends MinifetchError {
84
84
  */
85
85
  constructor(message: string, originalError?: Error);
86
86
  }
87
+ /**
88
+ * Thrown when keyword-search query validation fails (empty, or over 50 chars).
89
+ * The query sibling of {@link InvalidUrlError} — searchByKeyword takes a query,
90
+ * not a URL, so bad input surfaces here instead.
91
+ */
92
+ export declare class InvalidQueryError extends MinifetchError {
93
+ readonly query: string;
94
+ /**
95
+ *
96
+ * @param query
97
+ * @param message
98
+ */
99
+ constructor(query: string, message?: string);
100
+ }
101
+ /**
102
+ * Thrown when a keyword search fails (non-OK response or unexpected error).
103
+ * The search sibling of {@link ExtractionFailedError}: it carries the `query`
104
+ * rather than a `url`, since search has no target URL.
105
+ */
106
+ export declare class SearchFailedError extends MinifetchError {
107
+ readonly query: string;
108
+ readonly statusCode?: number;
109
+ readonly originalError?: Error;
110
+ /**
111
+ *
112
+ * @param query
113
+ * @param message
114
+ * @param statusCode
115
+ * @param originalError
116
+ */
117
+ constructor(query: string, message: string, statusCode?: number, originalError?: Error);
118
+ }
@@ -48,3 +48,38 @@ export interface PaymentInfo {
48
48
  /** Link to view transaction on block explorer **/
49
49
  explorerLink: string;
50
50
  }
51
+ /**
52
+ * A single keyword-search result.
53
+ */
54
+ export interface SearchKeywordResult {
55
+ /** Title of the result page */
56
+ title: string;
57
+ /** URL of the result page */
58
+ url: string;
59
+ /** Text snippet from the result page, trimmed to the requested descriptionLength */
60
+ description: string;
61
+ }
62
+ /**
63
+ * Response from the keyword search endpoint.
64
+ *
65
+ * Unlike PaidEndpointResponse, this preserves `queryParameters` — the effective
66
+ * request params after the server clamps `limit`/`descriptionLength` into range.
67
+ * That echo is how a caller learns what actually ran (no black box).
68
+ */
69
+ export interface SearchKeywordResponse {
70
+ /** Minifetch API success (200, ok) */
71
+ success: boolean;
72
+ /** Effective params after server-side clamping. */
73
+ queryParameters: {
74
+ query: string;
75
+ limit: number;
76
+ descriptionLength: number;
77
+ };
78
+ /** Ranked search results. */
79
+ results: Array<{
80
+ data: SearchKeywordResult;
81
+ error?: Record<string, any>;
82
+ }>;
83
+ /** Payment information — only present for paid x402 requests. */
84
+ payment?: PaymentInfo;
85
+ }
@@ -6,3 +6,12 @@
6
6
  * @throws {InvalidUrlError} if URL is invalid
7
7
  */
8
8
  export declare function validateAndNormalizeUrl(url: string): string;
9
+ /**
10
+ * Validate and normalize a keyword-search query.
11
+ * Mirrors the server: strips null bytes, normalizes unicode, trims, then enforces
12
+ * the same non-empty / 50-char rules so bad input fails before a paid call.
13
+ *
14
+ * @param query
15
+ * @throws {InvalidQueryError} if the query is empty or exceeds 50 characters
16
+ */
17
+ export declare function validateAndNormalizeQuery(query: string): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "minifetch-api",
3
- "version": "1.6.0",
4
- "description": "Minifetch.com API Client. Scrape, extract and audit web pages. Pay per URL, no subscription.",
3
+ "version": "1.7.0",
4
+ "description": "Minifetch.com API Client. Search, scrape, extract and SEO audit web pages. Pay per fetch, no subscription.",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -25,6 +25,8 @@
25
25
  "metadata extraction",
26
26
  "content extraction",
27
27
  "link extraction",
28
+ "search",
29
+ "keyword search",
28
30
  "hosted",
29
31
  "meta tags",
30
32
  "open graph",