minifetch-api 1.3.4 → 1.5.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 +37 -25
- package/dist/esm/client.js +16 -9
- package/dist/esm/utils/payment.js +21 -3
- package/dist/types/client.d.ts +13 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,19 +5,17 @@
|
|
|
5
5
|
</a>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
**[Minifetch](https://minifetch.com) is a hosted toolkit of extraction primitives
|
|
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.
|
|
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. 🎉🎉
|
|
12
12
|
- ⛔ No charge for blocked pages (403 errors).
|
|
13
13
|
|
|
14
14
|
---
|
|
15
|
+
|
|
15
16
|
**👉 [Full API docs](https://minifetch.com/docs/api) | [LLMs.txt](https://minifetch.com/llms.txt) and [SKILL.md](https://minifetch.com/SKILL.md) for agents | Questions? Join our [Discord](https://discord.gg/EM6ET8Dshm)**
|
|
16
17
|
|
|
17
18
|
---
|
|
18
|
-
**Payments.** Two ways to pay:
|
|
19
|
-
1. Credit card + API key. Get started free - [visit our dashboard to Sign Up](https://minifetch.com/dashboard). Create a Minifetch account and it will be auto-loaded with 25 free technical SEO page audits. Top up with your credit card later.
|
|
20
|
-
2. USDC on Base or Solana. Just load your wallet with USDC, you're ready. No "gas token" (ETH or SOL) required. You don't need a Minifetch account either, just load your wallet and go!
|
|
21
19
|
|
|
22
20
|
## Prerequisites
|
|
23
21
|
|
|
@@ -25,9 +23,15 @@
|
|
|
25
23
|
- A valid API key from our [dashboard](https://minifetch.com/dashboard)
|
|
26
24
|
- ***Or*** an Ethereum or Solana private key for making USDC payments on Base or Solana networks.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
**Payments.** Two ways to pay:
|
|
27
|
+
1. Credit card + API key. Get started free - [visit our dashboard to Sign Up](https://minifetch.com/dashboard). Create a Minifetch account and it will be auto-loaded with 25 free technical SEO page audits. Top up with your credit card later.
|
|
28
|
+
2. USDC on Base or Solana. Just load your wallet with USDC, you're ready. No "gas token" (ETH or SOL) required. You don't need a Minifetch account either, just load your wallet and go!
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
## Install
|
|
31
|
+
Add to your project:
|
|
32
|
+
```
|
|
33
|
+
npm install minifetch-api --save`
|
|
34
|
+
```
|
|
31
35
|
|
|
32
36
|
## Quick Start
|
|
33
37
|
|
|
@@ -66,9 +70,9 @@ try {
|
|
|
66
70
|
|
|
67
71
|
After the Quick Start, you have the following methods to use.
|
|
68
72
|
|
|
69
|
-
**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](https://github.com/Niche-Networks/minifetch-api/)
|
|
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/).
|
|
70
74
|
|
|
71
|
-
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 the
|
|
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).
|
|
72
76
|
|
|
73
77
|
```js
|
|
74
78
|
await client.checkAndRunSeoPageAudit(url);
|
|
@@ -82,15 +86,15 @@ await client.checkAndRunSeoPageAudit(url);
|
|
|
82
86
|
|
|
83
87
|
await client.checkAndExtractUrlMetadata(url, options);
|
|
84
88
|
// Price: $0.002
|
|
85
|
-
// Extracts rich structured metadata from your URL:
|
|
86
|
-
// description, canonical, SEO-related meta tags, Open Graph and
|
|
87
|
-
// Twitter card tags, JSON-LD and more.
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// and
|
|
91
|
-
// drop-in replacement for the npm `url-metadata` package.
|
|
89
|
+
// Extracts rich structured metadata from your URL: redirects, favicons,
|
|
90
|
+
// title, description, canonical, SEO-related meta tags, Open Graph and
|
|
91
|
+
// Twitter card tags, JSON-LD and more. Returns everything by default;
|
|
92
|
+
// narrow the response with the `fields` query param (pass atomic field
|
|
93
|
+
// names, ex: ?fields=title,description or the named groups: network,meta,
|
|
94
|
+
// og,twitter and/or meta:<name> for a specific page meta tag)
|
|
92
95
|
// Options:
|
|
93
|
-
// {
|
|
96
|
+
// { fields: ['canonical', 'og:url'] } - narrows to custom selected fields
|
|
97
|
+
// { omitEmpty: true } - defaults to false
|
|
94
98
|
// { includeResponseBody: true } - defaults to false
|
|
95
99
|
|
|
96
100
|
await client.checkAndExtractUrlLinks(url);
|
|
@@ -104,9 +108,8 @@ await client.checkAndExtractUrlLinks(url);
|
|
|
104
108
|
|
|
105
109
|
await client.checkAndExtractUrlPreview(url);
|
|
106
110
|
// Price: $0.002
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// platforms, chat apps and AI.
|
|
111
|
+
// Extracts all fields used for a page's share previews: the lightweight
|
|
112
|
+
// cards that represent the page on social platforms, chat apps, and AI.
|
|
110
113
|
|
|
111
114
|
await client.checkAndExtractUrlContent(url, options);
|
|
112
115
|
// Price: $0.002
|
|
@@ -114,10 +117,14 @@ await client.checkAndExtractUrlContent(url, options);
|
|
|
114
117
|
// an LLM extracts from your page after nav, ads, & scripts are stripped.
|
|
115
118
|
// See what survives for AEO and AI search; respects robots.txt.
|
|
116
119
|
// Options: { includeMediaUrls: true } - defaults to false.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
For max control, you can also use the following methods directly:
|
|
123
|
+
```js
|
|
124
|
+
await client.preflightCheck(url, options);
|
|
125
|
+
// Free: check if `minfetch` user agent can access target via robots.txt
|
|
126
|
+
// Options: { "fresh": true } - bypass 24hr robots.txt cache, defaults to false
|
|
117
127
|
|
|
118
|
-
// For max control, you can also use the following methods directly.
|
|
119
|
-
// Free: check if `minfetch` user agent can access target via robots.txt:
|
|
120
|
-
await client.preflightCheck(url);
|
|
121
128
|
// Paid methods:
|
|
122
129
|
await client.runSeoPageAudit(url);
|
|
123
130
|
await client.extractUrlMetadata(url, options); // same options as above
|
|
@@ -126,9 +133,12 @@ await client.extractUrlPreview(url);
|
|
|
126
133
|
await client.extractUrlContent(url, options); // same options as above
|
|
127
134
|
```
|
|
128
135
|
---
|
|
136
|
+
|
|
129
137
|
### Error Types
|
|
130
|
-
When you wrap the functions above in a try/catch, here are the errors you may encounter. You are never charged for URLs that are blocked or error.
|
|
138
|
+
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.
|
|
131
139
|
|
|
140
|
+
- **"InvalidURLError: Invalid url ${url}"**
|
|
141
|
+
- The URL is malformed in some way, correct it and try again.
|
|
132
142
|
- **"RobotsBlockedError: URL is blocked by robots.txt"**
|
|
133
143
|
- Minifetch is explicitly blocked by the website's `robots.txt`, cannot be fetched. If this is your site, read our tutorial [How To Unblock Minifetch](https://minifetch.com/tutorials/unblock-minifetch)
|
|
134
144
|
- **"Network Error: 402 Payment Required"**
|
|
@@ -139,9 +149,9 @@ When you wrap the functions above in a try/catch, here are the errors you may en
|
|
|
139
149
|
- URLs that pass their robots.txt check but are blocked anyway via 403 or other tactics may error like this. No charge.
|
|
140
150
|
- **"503 Service Temporarily Unavailable"**
|
|
141
151
|
- Likely encountering upstream timeout errors on the target URL.
|
|
142
|
-
|
|
143
|
-
- The URL is malformed in some way, correct it and try again.
|
|
152
|
+
|
|
144
153
|
---
|
|
154
|
+
|
|
145
155
|
### Service Limitations
|
|
146
156
|
Minifetch only extracts publicly available metadata and content from pages accessible without authentication and javascript execution.
|
|
147
157
|
|
|
@@ -156,6 +166,7 @@ What Minifetch does NOT do *currently* but may offer in the future as an add-on:
|
|
|
156
166
|
- Access authenticated or logged-in content
|
|
157
167
|
|
|
158
168
|
---
|
|
169
|
+
|
|
159
170
|
### USDC Best Practices
|
|
160
171
|
- Uses the [x402 Protocol](https://www.x402.org/). No "gas token" (ETH or SOL) required, just load your wallet with USDC on Base or Solana network.
|
|
161
172
|
- LLMs & Agents should *never* have direct access to your private key. They *will* expose it!
|
|
@@ -164,6 +175,7 @@ What Minifetch does NOT do *currently* but may offer in the future as an add-on:
|
|
|
164
175
|
- Pass your private key into the Minifetch API Client with an [environment variable](https://developer.vonage.com/en/blog/how-to-use-environment-variables-in-javascript-with-dotenv).
|
|
165
176
|
|
|
166
177
|
---
|
|
178
|
+
|
|
167
179
|
**License**
|
|
168
180
|
|
|
169
181
|
MIT / Copyright (c) 2026 Lauren Garcia
|
package/dist/esm/client.js
CHANGED
|
@@ -22,13 +22,18 @@ export class MinifetchClient {
|
|
|
22
22
|
* Check if URL is allowed by robots.txt (free preflight check — no auth required)
|
|
23
23
|
*
|
|
24
24
|
* @param url
|
|
25
|
+
* @param options
|
|
26
|
+
* @param options.fresh
|
|
25
27
|
* @throws {InvalidUrlError} if URL is invalid
|
|
26
28
|
* @throws {NetworkError} if request fails
|
|
27
29
|
*/
|
|
28
|
-
async preflightUrlCheck(url) {
|
|
30
|
+
async preflightUrlCheck(url, options) {
|
|
29
31
|
try {
|
|
30
32
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
31
|
-
const
|
|
33
|
+
const params = new URLSearchParams({ url: normalizedUrl });
|
|
34
|
+
if (options?.fresh)
|
|
35
|
+
params.set("fresh", "true");
|
|
36
|
+
const requestUrl = `${this.baseUrl}/api/v1/free/preflight/url-check?${params.toString()}`;
|
|
32
37
|
const response = await fetch(requestUrl);
|
|
33
38
|
if (!response.ok) {
|
|
34
39
|
throw new NetworkError(`Preflight check failed: ${response.status} ${response.statusText}`);
|
|
@@ -67,7 +72,8 @@ export class MinifetchClient {
|
|
|
67
72
|
*
|
|
68
73
|
* @param url
|
|
69
74
|
* @param options
|
|
70
|
-
* @param options.
|
|
75
|
+
* @param options.fields
|
|
76
|
+
* @param options.omitEmpty
|
|
71
77
|
* @param options.includeResponseBody
|
|
72
78
|
* @throws {InvalidUrlError} if URL is invalid
|
|
73
79
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
@@ -78,8 +84,10 @@ export class MinifetchClient {
|
|
|
78
84
|
try {
|
|
79
85
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
80
86
|
const params = new URLSearchParams({ url: normalizedUrl });
|
|
81
|
-
if (options?.
|
|
82
|
-
params.set("
|
|
87
|
+
if (options?.fields?.length)
|
|
88
|
+
params.set("fields", options.fields.join(","));
|
|
89
|
+
if (options?.omitEmpty)
|
|
90
|
+
params.set("omitEmpty", "true");
|
|
83
91
|
if (options?.includeResponseBody)
|
|
84
92
|
params.set("includeResponseBody", "true");
|
|
85
93
|
const requestUrl = `${this.baseUrl}${this._paidPath("/extract/url-metadata")}?${params.toString()}`;
|
|
@@ -167,7 +175,8 @@ export class MinifetchClient {
|
|
|
167
175
|
*
|
|
168
176
|
* @param url
|
|
169
177
|
* @param options
|
|
170
|
-
* @param options.
|
|
178
|
+
* @param options.fields
|
|
179
|
+
* @param options.omitEmpty
|
|
171
180
|
* @param options.includeResponseBody
|
|
172
181
|
*/
|
|
173
182
|
async checkAndExtractUrlMetadata(url, options) {
|
|
@@ -217,9 +226,7 @@ export class MinifetchClient {
|
|
|
217
226
|
* @param endpoint
|
|
218
227
|
*/
|
|
219
228
|
_paidPath(endpoint) {
|
|
220
|
-
return this.config.authMode === "x402"
|
|
221
|
-
? `/api/v1/x402${endpoint}`
|
|
222
|
-
: `/api/v1${endpoint}`;
|
|
229
|
+
return this.config.authMode === "x402" ? `/api/v1/x402${endpoint}` : `/api/v1${endpoint}`;
|
|
223
230
|
}
|
|
224
231
|
/**
|
|
225
232
|
* Dispatch to the correct request handler based on auth mode, then
|
|
@@ -29,7 +29,7 @@ export async function handlePayment(url, config) {
|
|
|
29
29
|
}
|
|
30
30
|
else if (isSolana) {
|
|
31
31
|
if (!config.privateKey)
|
|
32
|
-
throw new PaymentFailedError(
|
|
32
|
+
throw new PaymentFailedError("privateKey is required for Solana payments");
|
|
33
33
|
const privateKeyBytes = bs58.decode(config.privateKey);
|
|
34
34
|
const signer = await createKeyPairSignerFromBytes(privateKeyBytes);
|
|
35
35
|
const svmSigner = signer;
|
|
@@ -42,7 +42,8 @@ export async function handlePayment(url, config) {
|
|
|
42
42
|
const fetchWithPayment = wrapFetchWithPayment(fetch, _x402Client);
|
|
43
43
|
const response = await fetchWithPayment(url, { method: "GET" });
|
|
44
44
|
if (!response.ok) {
|
|
45
|
-
|
|
45
|
+
const serverMessage = await readServerErrorMessage(response);
|
|
46
|
+
throw new NetworkError(`Request failed: ${response.status} ${response.statusText}${serverMessage ? ` — ${serverMessage}` : ""}`);
|
|
46
47
|
}
|
|
47
48
|
const httpClient = new x402HTTPClient(_x402Client);
|
|
48
49
|
const paymentResponse = httpClient.getPaymentSettleResponse(name => response.headers.get(name));
|
|
@@ -79,7 +80,8 @@ export async function handleApiKeyRequest(url, config) {
|
|
|
79
80
|
},
|
|
80
81
|
});
|
|
81
82
|
if (!response.ok) {
|
|
82
|
-
|
|
83
|
+
const serverMessage = await readServerErrorMessage(response);
|
|
84
|
+
throw new NetworkError(`Request failed: ${response.status} ${response.statusText}${serverMessage ? ` — ${serverMessage}` : ""}`);
|
|
83
85
|
}
|
|
84
86
|
return { response };
|
|
85
87
|
}
|
|
@@ -101,3 +103,19 @@ function getExplorerLink(config, txHash) {
|
|
|
101
103
|
return "";
|
|
102
104
|
}
|
|
103
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Best-effort read of the rich server error message from a non-ok response.
|
|
108
|
+
* Minifetch error bodies carry it at results[0].error.message.
|
|
109
|
+
*
|
|
110
|
+
* @param response - non-ok Response (body is consumed)
|
|
111
|
+
*/
|
|
112
|
+
async function readServerErrorMessage(response) {
|
|
113
|
+
try {
|
|
114
|
+
const body = (await response.json());
|
|
115
|
+
// TODO: revisit this approach when we scale up to multiple results per request
|
|
116
|
+
return body?.results?.[0]?.error?.message;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -17,10 +17,14 @@ export declare class MinifetchClient {
|
|
|
17
17
|
* Check if URL is allowed by robots.txt (free preflight check — no auth required)
|
|
18
18
|
*
|
|
19
19
|
* @param url
|
|
20
|
+
* @param options
|
|
21
|
+
* @param options.fresh
|
|
20
22
|
* @throws {InvalidUrlError} if URL is invalid
|
|
21
23
|
* @throws {NetworkError} if request fails
|
|
22
24
|
*/
|
|
23
|
-
preflightUrlCheck(url: string
|
|
25
|
+
preflightUrlCheck(url: string, options?: {
|
|
26
|
+
fresh?: boolean;
|
|
27
|
+
}): Promise<PreflightCheckResponse>;
|
|
24
28
|
/**
|
|
25
29
|
* Run SEO page audit (paid endpoint)
|
|
26
30
|
*
|
|
@@ -36,7 +40,8 @@ export declare class MinifetchClient {
|
|
|
36
40
|
*
|
|
37
41
|
* @param url
|
|
38
42
|
* @param options
|
|
39
|
-
* @param options.
|
|
43
|
+
* @param options.fields
|
|
44
|
+
* @param options.omitEmpty
|
|
40
45
|
* @param options.includeResponseBody
|
|
41
46
|
* @throws {InvalidUrlError} if URL is invalid
|
|
42
47
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
@@ -44,7 +49,8 @@ export declare class MinifetchClient {
|
|
|
44
49
|
* @throws {NetworkError} various reasons, check README
|
|
45
50
|
*/
|
|
46
51
|
extractUrlMetadata(url: string, options?: {
|
|
47
|
-
|
|
52
|
+
fields?: string[];
|
|
53
|
+
omitEmpty?: boolean;
|
|
48
54
|
includeResponseBody?: boolean;
|
|
49
55
|
}): Promise<PaidEndpointResponse>;
|
|
50
56
|
/**
|
|
@@ -94,11 +100,13 @@ export declare class MinifetchClient {
|
|
|
94
100
|
*
|
|
95
101
|
* @param url
|
|
96
102
|
* @param options
|
|
97
|
-
* @param options.
|
|
103
|
+
* @param options.fields
|
|
104
|
+
* @param options.omitEmpty
|
|
98
105
|
* @param options.includeResponseBody
|
|
99
106
|
*/
|
|
100
107
|
checkAndExtractUrlMetadata(url: string, options?: {
|
|
101
|
-
|
|
108
|
+
fields?: string[];
|
|
109
|
+
omitEmpty?: boolean;
|
|
102
110
|
includeResponseBody?: boolean;
|
|
103
111
|
}): Promise<PaidEndpointResponse>;
|
|
104
112
|
/**
|
package/package.json
CHANGED