minifetch-api 1.1.0 → 1.2.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 +76 -54
- package/dist/esm/client.js +115 -140
- package/dist/esm/init.js +44 -13
- package/dist/esm/utils/payment.js +27 -22
- package/dist/types/client.d.ts +57 -24
- package/dist/types/init.d.ts +3 -2
- package/dist/types/types/config.d.ts +23 -13
- package/dist/types/types/responses.d.ts +2 -2
- package/dist/types/utils/payment.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,18 +5,27 @@
|
|
|
5
5
|
</a>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
**Fetch & extract data from web pages.** [Minifetch.com](https://minifetch.com) provides composable extraction APIs
|
|
8
|
+
**Fetch & extract data from web pages.** [Minifetch.com](https://minifetch.com) provides composable extraction APIs for humans and AI Agents-- making web pages simple to access.
|
|
9
|
+
|
|
10
|
+
- **✅ [Sign up](https://minifetch.com/dashboard) for an account and get your first 125-250 fetches for free. 🎉🎉**
|
|
11
|
+
- ✅ Always pay-as-you-go at competitive prices.
|
|
12
|
+
- ⛔ No charge for blocked pages (403 errors).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
**👉 [Dashboard](https://minifetch.com/dashboard) | [Full API docs](https://minifetch.com/docs/api) | [LLMs.txt](https://minifetch.com/llms.txt) | Questions? Join our [Discord]([Discord](https://discord.gg/EM6ET8Dshm))**
|
|
9
16
|
|
|
10
|
-
**Full [API docs are here](https://minifetch.com/docs/api)**, including example queries, response data & prices. For AI Agents, read the [LLMs.txt](https://minifetch.com/llms.txt) instead.
|
|
11
17
|
|
|
12
|
-
**Payments** work with [x402](https://www.x402.org/) USDC stablecoin micropayments on Coinbase's Base & Solana blockchain networks. Transaction fees are free. If you'd like to use a traditional credit card & API key instead, sign up for the waitlist: [forms.gle/rkMi7T23bHJc8XFw9](https://forms.gle/rkMi7T23bHJc8XFw9)
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
---
|
|
20
|
+
**Payments.** Two ways to pay:
|
|
21
|
+
1. Credit card + API key. Get started free - [visit our dashboard to Sign Up](https://minifetch.com/dashboard). Your account will be auto-loaded with 125–250 API calls. Top up with your credit card later.
|
|
22
|
+
2. x402. USDC stablecoin micropayments on Coinbase's Base & Solana networks.
|
|
15
23
|
|
|
16
24
|
## Prerequisites
|
|
17
25
|
|
|
18
26
|
- Node.js v18+ & NPM
|
|
19
|
-
- A valid
|
|
27
|
+
- A valid API key from our [dashboard](https://minifetch.com/dashboard)
|
|
28
|
+
- *Or* an Ethereum or Solana private key for making USDC payments on Base or Solana networks.
|
|
20
29
|
|
|
21
30
|
## Install
|
|
22
31
|
|
|
@@ -27,82 +36,95 @@
|
|
|
27
36
|
```js
|
|
28
37
|
import Minifetch from "minifetch-api";
|
|
29
38
|
|
|
30
|
-
//
|
|
39
|
+
// First, initialize the client with your payment choice:
|
|
40
|
+
|
|
41
|
+
// 1. API Key Payments:
|
|
42
|
+
const client = new Minifetch({
|
|
43
|
+
apiKey: process.env.MINIFETCH_API_KEY
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 2. x402 Payments:
|
|
31
47
|
// - Network options: "base" or "solana"
|
|
32
|
-
// -
|
|
48
|
+
// - Private key from wallet that has a small amt of USDC
|
|
33
49
|
const client = new Minifetch({
|
|
34
50
|
network: "base",
|
|
35
51
|
privateKey: process.env.BASE_PRIVATE_KEY,
|
|
36
52
|
});
|
|
37
53
|
|
|
54
|
+
// Now you're ready to extract data from a url:
|
|
38
55
|
try {
|
|
39
56
|
const url = "example.com";
|
|
40
|
-
const response = await client.
|
|
41
|
-
// 200 "ok" responses
|
|
57
|
+
const response = await client.checkAndExtractUrlPreview(url);
|
|
58
|
+
// 200 "ok" responses
|
|
42
59
|
console.log("data: ", response.data);
|
|
43
|
-
console.log("payment info: ", response.payment);
|
|
44
60
|
} catch (err) {
|
|
45
|
-
// No
|
|
61
|
+
// No charge for errors or 403 blocked urls!
|
|
46
62
|
console.log(err);
|
|
47
63
|
}
|
|
48
64
|
```
|
|
49
65
|
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
After you initialize the client (above), you have the following methods available to use. The "checkAndExtract" API methods provided help you avoid paying for blocked URLs and return more granular info about why a URL may or may not return data. There is no charge for blocked upstream URLs.
|
|
53
|
-
|
|
54
|
-
**Price list** and example data for each method [is here.](https://minifetch.com/docs/api#example-data)
|
|
55
|
-
|
|
56
|
-
**Rate limit** Requests exceeding rate limits return `503` errors. This is intentional — back off and retry, max 5-10 queries per second. We'll add bulk fetch and extract in the future. Sign up for the [waitlist](https://forms.gle/rkMi7T23bHJc8XFw9).
|
|
57
|
-
|
|
58
|
-
**Code examples** can be found in the github repository under /test.
|
|
66
|
+
### Data Extraction Methods
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
- Extracts a light, token-efficient preview of a URL: title, description, and image (only).
|
|
68
|
+
After the Quick Start, you have the following methods to use. The "checkAndExtract" methods help to avoid paying for blocked URLs. **Price list** & example data [is here.](https://minifetch.com/docs/api#example-data)
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
- Extracts a clean, token-efficient content summary as markdown from a URL. Removes ads, nav, scripts. Much more efficient than raw HTML fetches for LLMs.
|
|
65
|
-
- Option: `{ includeMediaUrls: true }`, defaults to false
|
|
70
|
+
**Wrap** these in a **try/catch** just like in the Quickstart example above. **Code examples** can be also found in the [Github repository](https://github.com/Niche-Networks/minifetch-api/) in the /example- directories.
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
```js
|
|
73
|
+
// Extracts a light, token-efficient preview of a URL:
|
|
74
|
+
// title, description, and image (only).
|
|
75
|
+
client.checkAndExtractUrlPreview(url);
|
|
76
|
+
|
|
77
|
+
// Extracts a clean, token-efficient content summary as markdown from a URL.
|
|
78
|
+
// Removes ads, nav, scripts. More efficient than raw HTML fetches for LLMs.
|
|
79
|
+
// Options:
|
|
80
|
+
// { includeMediaUrls: true }, defaults to false.
|
|
81
|
+
client.checkAndExtractUrlContent(url, options);
|
|
82
|
+
|
|
83
|
+
// Extracts all links from a URL categorized by type (internal/external/anchor).
|
|
84
|
+
// Detects image links, nofollow attributes, analyzes external domain distribution.
|
|
85
|
+
client.checkAndExtractUrlLinks(url);
|
|
86
|
+
|
|
87
|
+
// Extracts rich structured metadata from a URL:
|
|
88
|
+
// meta tags, json-ld, images, headings, response headers, + more.
|
|
89
|
+
// Perfect for SEO research or metadata indexing & analysis. Largest
|
|
90
|
+
// response size of client methods. Setting verbosity to "full" is the drop-in
|
|
91
|
+
// replacement for the [`url-metadata`](https://www.npmjs.com/package/url-metadata) package.
|
|
92
|
+
// Options:
|
|
93
|
+
// { verbosity: "full" }, defaults to "standard"
|
|
94
|
+
// { includeResponseBody: true }, defaults to false
|
|
95
|
+
client.checkAndExtractUrlMetadata(url, options);
|
|
96
|
+
|
|
97
|
+
// For max control, you can also use the following methods directly.
|
|
98
|
+
// Free - check robots.txt:
|
|
99
|
+
client.preflightCheck(url);
|
|
100
|
+
// Extract data:
|
|
101
|
+
client.extractUrlPreview(url);
|
|
102
|
+
client.extractUrlContent(url, options); // same options as above
|
|
103
|
+
client.extractUrlLinks(url);
|
|
104
|
+
client.extractUrlMetadata(url, options) // same options as above
|
|
86
105
|
|
|
106
|
+
```
|
|
107
|
+
---
|
|
87
108
|
### Error Types
|
|
88
|
-
When you wrap the functions above in a try/catch, here are the errors you may encounter
|
|
109
|
+
When you wrap the functions above in a try/catch, here are the errors you may encounter. You are never charged for errors.
|
|
89
110
|
|
|
90
111
|
- **"RobotsBlockedError: URL is blocked by robots.txt"**
|
|
91
|
-
- URL is explicitly blocked by the website's robots.txt
|
|
112
|
+
- URL is explicitly blocked by the website's robots.txt, cannot be fetched.
|
|
92
113
|
- **"Network Error: 402 Payment Required"**
|
|
93
114
|
- Check your wallet -- likely you ran out of USDC to pay!
|
|
115
|
+
- **"Network Error: 429 Too Many Requests"**
|
|
116
|
+
- Back off and retry, max 5-10 requests per second.
|
|
94
117
|
- **"Network Error: 502 Bad Gateway"**
|
|
95
|
-
- URLs that pass
|
|
118
|
+
- URLs that pass their robots.txt check but are blocked anyway via 403 or other tactics may error like this.
|
|
96
119
|
- **"503 Service Temporarily Unavailable"**
|
|
97
|
-
- Likely encountering
|
|
120
|
+
- Likely encountering upstream timeout errors on the target URL.
|
|
98
121
|
- **"InvalidURLError: Invalid url ${url}"**
|
|
99
|
-
- The URL
|
|
100
|
-
|
|
122
|
+
- The URL is malformed in some way, correct it and try again.
|
|
123
|
+
---
|
|
101
124
|
### Service Limitations
|
|
102
125
|
Minifetch only extracts publicly available metadata and content from pages accessible without authentication and javascript execution.
|
|
103
126
|
|
|
104
|
-
What Minifetch does NOT do:
|
|
105
|
-
|
|
127
|
+
What Minifetch does *NOT* do:
|
|
106
128
|
- Ignore robots.txt directives
|
|
107
129
|
- Access authenticated or logged-in content
|
|
108
130
|
- Create accounts or log into user sessions
|
|
@@ -112,8 +134,8 @@ What Minifetch does NOT do:
|
|
|
112
134
|
What Minifetch does NOT do *currently* but may offer in the future as an add-on:
|
|
113
135
|
- Javascript execution
|
|
114
136
|
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
---
|
|
138
|
+
### x402 Best Practices
|
|
117
139
|
- LLMs & Agents should *never* have direct access to your private key. They *will* expose it!
|
|
118
140
|
- Only keep a small amount of USDC in the account whose private key you use.
|
|
119
141
|
- Keep that account separate from the rest of your onchain funds.
|
package/dist/esm/client.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { initConfig } from "./init.js";
|
|
2
2
|
import { validateAndNormalizeUrl } from "./utils/validation.js";
|
|
3
|
-
import { handlePayment } from "./utils/payment.js";
|
|
3
|
+
import { handlePayment, handleApiKeyRequest } from "./utils/payment.js";
|
|
4
4
|
import { InvalidUrlError, RobotsBlockedError, PaymentFailedError, ExtractionFailedError, NetworkError, } from "./types/errors.js";
|
|
5
5
|
/**
|
|
6
|
-
* Main Minifetch API client
|
|
7
|
-
*
|
|
6
|
+
* Main Minifetch API client.
|
|
7
|
+
* Supports two auth modes:
|
|
8
|
+
* - x402: crypto micropayments via Coinbase x402 (pass network + privateKey)
|
|
9
|
+
* - apiKey: Stripe-backed credits (pass apiKey: "mf_prod_..." or "mf_dev_...")
|
|
8
10
|
*/
|
|
9
11
|
export class MinifetchClient {
|
|
10
12
|
config;
|
|
11
13
|
baseUrl;
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param config
|
|
15
|
+
* @param config - Either { network, privateKey } for x402 or { apiKey } for API key auth
|
|
15
16
|
*/
|
|
16
17
|
constructor(config) {
|
|
17
18
|
this.config = initConfig(config);
|
|
18
19
|
this.baseUrl = this.config.apiBaseUrl;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* Check if URL is allowed by robots.txt (free preflight check)
|
|
22
|
+
* Check if URL is allowed by robots.txt (free preflight check — no auth required)
|
|
22
23
|
*
|
|
23
24
|
* @param url
|
|
24
25
|
* @throws {InvalidUrlError} if URL is invalid
|
|
@@ -26,17 +27,13 @@ export class MinifetchClient {
|
|
|
26
27
|
*/
|
|
27
28
|
async preflightUrlCheck(url) {
|
|
28
29
|
try {
|
|
29
|
-
// Validate and normalize URL
|
|
30
30
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
31
|
-
|
|
32
|
-
const endpoint = `/api/v1/free/preflight/url-check?url=${encodeURIComponent(normalizedUrl)}`;
|
|
33
|
-
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
31
|
+
const requestUrl = `${this.baseUrl}/api/v1/free/preflight/url-check?url=${encodeURIComponent(normalizedUrl)}`;
|
|
34
32
|
const response = await fetch(requestUrl);
|
|
35
33
|
if (!response.ok) {
|
|
36
34
|
throw new NetworkError(`Preflight check failed: ${response.status} ${response.statusText}`);
|
|
37
35
|
}
|
|
38
|
-
|
|
39
|
-
return data;
|
|
36
|
+
return (await response.json());
|
|
40
37
|
}
|
|
41
38
|
catch (error) {
|
|
42
39
|
if (error instanceof InvalidUrlError || error instanceof NetworkError) {
|
|
@@ -46,230 +43,208 @@ export class MinifetchClient {
|
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
/**
|
|
49
|
-
* Extract URL metadata (paid
|
|
46
|
+
* Extract URL metadata (paid endpoint)
|
|
50
47
|
*
|
|
51
48
|
* @param url
|
|
52
49
|
* @param options
|
|
50
|
+
* @param options.verbosity - "standard" (default) or "full"
|
|
53
51
|
* @param options.includeResponseBody
|
|
54
|
-
* @param options.verbosity - Controls response detail level: "standard" (default) or "full"
|
|
55
52
|
* @throws {InvalidUrlError} if URL is invalid
|
|
56
53
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
57
|
-
* @throws {PaymentFailedError} if payment fails
|
|
54
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
58
55
|
* @throws {NetworkError} various reasons, check README
|
|
59
56
|
*/
|
|
60
57
|
async extractUrlMetadata(url, options) {
|
|
61
58
|
try {
|
|
62
|
-
// Validate and normalize URL
|
|
63
59
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
64
|
-
// Build request URL with optional params
|
|
65
60
|
const params = new URLSearchParams({ url: normalizedUrl });
|
|
66
|
-
if (options?.verbosity)
|
|
61
|
+
if (options?.verbosity)
|
|
67
62
|
params.set("verbosity", options.verbosity);
|
|
68
|
-
|
|
69
|
-
if (options?.includeResponseBody) {
|
|
63
|
+
if (options?.includeResponseBody)
|
|
70
64
|
params.set("includeResponseBody", "true");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const endpoint = `/api/v1/x402/extract/url-metadata?${params.toString()}`;
|
|
74
|
-
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
75
|
-
// Make request with x402 payment handling
|
|
76
|
-
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
77
|
-
// Check if extraction succeeded
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
throw new ExtractionFailedError(normalizedUrl, `Metadata extraction failed: ${response.status} ${response.statusText}`);
|
|
80
|
-
}
|
|
81
|
-
const data = (await response.json());
|
|
82
|
-
return {
|
|
83
|
-
success: data.success,
|
|
84
|
-
results: data.results,
|
|
85
|
-
payment,
|
|
86
|
-
};
|
|
65
|
+
const requestUrl = `${this.baseUrl}${this._extractPath("url-metadata")}?${params.toString()}`;
|
|
66
|
+
return await this._makeRequest(requestUrl, normalizedUrl, "Metadata extraction");
|
|
87
67
|
}
|
|
88
68
|
catch (error) {
|
|
89
|
-
|
|
90
|
-
error instanceof ExtractionFailedError ||
|
|
91
|
-
error instanceof PaymentFailedError ||
|
|
92
|
-
error instanceof NetworkError) {
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
throw new ExtractionFailedError(url, `Metadata extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
69
|
+
return this._rethrowExtraction(error, url, "Metadata extraction");
|
|
96
70
|
}
|
|
97
71
|
}
|
|
98
72
|
/**
|
|
99
|
-
* Extract URL links (paid
|
|
73
|
+
* Extract URL links (paid endpoint)
|
|
100
74
|
*
|
|
101
75
|
* @param url
|
|
102
76
|
* @throws {InvalidUrlError} if URL is invalid
|
|
103
77
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
104
|
-
* @throws {PaymentFailedError} if payment fails
|
|
78
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
105
79
|
* @throws {NetworkError} various reasons, check README
|
|
106
80
|
*/
|
|
107
81
|
async extractUrlLinks(url) {
|
|
108
82
|
try {
|
|
109
83
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
// Make request with x402 payment handling
|
|
113
|
-
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
114
|
-
if (!response.ok) {
|
|
115
|
-
throw new ExtractionFailedError(normalizedUrl, `Links extraction failed: ${response.status} ${response.statusText}`);
|
|
116
|
-
}
|
|
117
|
-
const data = (await response.json());
|
|
118
|
-
return {
|
|
119
|
-
success: data.success,
|
|
120
|
-
results: data.results,
|
|
121
|
-
payment,
|
|
122
|
-
};
|
|
84
|
+
const requestUrl = `${this.baseUrl}${this._extractPath("url-links")}?url=${encodeURIComponent(normalizedUrl)}`;
|
|
85
|
+
return await this._makeRequest(requestUrl, normalizedUrl, "Links extraction");
|
|
123
86
|
}
|
|
124
87
|
catch (error) {
|
|
125
|
-
|
|
126
|
-
error instanceof ExtractionFailedError ||
|
|
127
|
-
error instanceof PaymentFailedError ||
|
|
128
|
-
error instanceof NetworkError) {
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
throw new ExtractionFailedError(url, `Links extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
88
|
+
return this._rethrowExtraction(error, url, "Links extraction");
|
|
132
89
|
}
|
|
133
90
|
}
|
|
134
91
|
/**
|
|
135
|
-
* Extract URL preview (paid
|
|
92
|
+
* Extract URL preview (paid endpoint)
|
|
136
93
|
*
|
|
137
94
|
* @param url
|
|
138
95
|
* @throws {InvalidUrlError} if URL is invalid
|
|
139
96
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
140
|
-
* @throws {PaymentFailedError} if payment fails
|
|
97
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
141
98
|
* @throws {NetworkError} various reasons, check README
|
|
142
99
|
*/
|
|
143
100
|
async extractUrlPreview(url) {
|
|
144
101
|
try {
|
|
145
|
-
// Validate and normalize URL
|
|
146
102
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
150
|
-
// Make request with x402 payment handling
|
|
151
|
-
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
152
|
-
// Check if extraction succeeded
|
|
153
|
-
if (!response.ok) {
|
|
154
|
-
throw new ExtractionFailedError(normalizedUrl, `Preview extraction failed: ${response.status} ${response.statusText}`);
|
|
155
|
-
}
|
|
156
|
-
const data = (await response.json());
|
|
157
|
-
return {
|
|
158
|
-
success: data.success,
|
|
159
|
-
results: data.results,
|
|
160
|
-
payment,
|
|
161
|
-
};
|
|
103
|
+
const requestUrl = `${this.baseUrl}${this._extractPath("url-preview")}?url=${encodeURIComponent(normalizedUrl)}`;
|
|
104
|
+
return await this._makeRequest(requestUrl, normalizedUrl, "Preview extraction");
|
|
162
105
|
}
|
|
163
106
|
catch (error) {
|
|
164
|
-
|
|
165
|
-
error instanceof ExtractionFailedError ||
|
|
166
|
-
error instanceof PaymentFailedError ||
|
|
167
|
-
error instanceof NetworkError) {
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
170
|
-
throw new ExtractionFailedError(url, `Preview extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
107
|
+
return this._rethrowExtraction(error, url, "Preview extraction");
|
|
171
108
|
}
|
|
172
109
|
}
|
|
173
110
|
/**
|
|
174
|
-
* Extract URL content as markdown (paid
|
|
111
|
+
* Extract URL content as markdown (paid endpoint)
|
|
175
112
|
*
|
|
176
113
|
* @param url
|
|
177
114
|
* @param options
|
|
178
115
|
* @param options.includeMediaUrls
|
|
179
116
|
* @throws {InvalidUrlError} if URL is invalid
|
|
180
117
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
181
|
-
* @throws {PaymentFailedError} if payment fails
|
|
118
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
182
119
|
* @throws {NetworkError} various reasons, check README
|
|
183
120
|
*/
|
|
184
121
|
async extractUrlContent(url, options) {
|
|
185
122
|
try {
|
|
186
|
-
// Validate and normalize URL
|
|
187
123
|
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
188
|
-
// Build request URL with optional params
|
|
189
124
|
const params = new URLSearchParams({ url: normalizedUrl });
|
|
190
|
-
if (options?.includeMediaUrls)
|
|
125
|
+
if (options?.includeMediaUrls)
|
|
191
126
|
params.set("includeMediaUrls", "true");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
195
|
-
// Make request with x402 payment handling
|
|
196
|
-
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
197
|
-
// Check if extraction succeeded
|
|
198
|
-
if (!response.ok) {
|
|
199
|
-
throw new ExtractionFailedError(normalizedUrl, `Content extraction failed: ${response.status} ${response.statusText}`);
|
|
200
|
-
}
|
|
201
|
-
const data = (await response.json());
|
|
202
|
-
return {
|
|
203
|
-
success: data.success,
|
|
204
|
-
results: data.results,
|
|
205
|
-
payment,
|
|
206
|
-
};
|
|
127
|
+
const requestUrl = `${this.baseUrl}${this._extractPath("url-content")}?${params.toString()}`;
|
|
128
|
+
return await this._makeRequest(requestUrl, normalizedUrl, "Content extraction");
|
|
207
129
|
}
|
|
208
130
|
catch (error) {
|
|
209
|
-
|
|
210
|
-
error instanceof ExtractionFailedError ||
|
|
211
|
-
error instanceof PaymentFailedError ||
|
|
212
|
-
error instanceof NetworkError) {
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
throw new ExtractionFailedError(url, `Content extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
131
|
+
return this._rethrowExtraction(error, url, "Content extraction");
|
|
216
132
|
}
|
|
217
133
|
}
|
|
218
134
|
/**
|
|
219
|
-
* Check URL
|
|
220
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
135
|
+
* Check URL then extract metadata in one call.
|
|
136
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
221
137
|
*
|
|
222
138
|
* @param url
|
|
223
139
|
* @param options
|
|
224
|
-
* @param options.verbosity -
|
|
225
|
-
* @param options.includeResponseBody
|
|
140
|
+
* @param options.verbosity - "standard" (default) or "full"
|
|
141
|
+
* @param options.includeResponseBody
|
|
226
142
|
*/
|
|
227
143
|
async checkAndExtractUrlMetadata(url, options) {
|
|
228
|
-
|
|
229
|
-
if (!checkResponse.results[0]?.data?.allowed) {
|
|
230
|
-
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
231
|
-
}
|
|
144
|
+
await this._preflightOrThrow(url);
|
|
232
145
|
return this.extractUrlMetadata(url, options);
|
|
233
146
|
}
|
|
234
147
|
/**
|
|
235
|
-
* Check URL
|
|
236
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
148
|
+
* Check URL then extract links in one call.
|
|
149
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
237
150
|
*
|
|
238
151
|
* @param url
|
|
239
152
|
*/
|
|
240
153
|
async checkAndExtractUrlLinks(url) {
|
|
241
|
-
|
|
242
|
-
if (!checkResponse.results[0]?.data?.allowed) {
|
|
243
|
-
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
244
|
-
}
|
|
154
|
+
await this._preflightOrThrow(url);
|
|
245
155
|
return this.extractUrlLinks(url);
|
|
246
156
|
}
|
|
247
157
|
/**
|
|
248
|
-
* Check URL
|
|
249
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
158
|
+
* Check URL then extract preview in one call.
|
|
159
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
250
160
|
*
|
|
251
161
|
* @param url
|
|
252
162
|
*/
|
|
253
163
|
async checkAndExtractUrlPreview(url) {
|
|
254
|
-
|
|
255
|
-
if (!checkResponse.results[0]?.data?.allowed) {
|
|
256
|
-
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
257
|
-
}
|
|
164
|
+
await this._preflightOrThrow(url);
|
|
258
165
|
return this.extractUrlPreview(url);
|
|
259
166
|
}
|
|
260
167
|
/**
|
|
261
|
-
* Check URL
|
|
262
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
168
|
+
* Check URL then extract content in one call.
|
|
169
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
263
170
|
*
|
|
264
171
|
* @param url
|
|
265
172
|
* @param options
|
|
266
173
|
* @param options.includeMediaUrls
|
|
267
174
|
*/
|
|
268
175
|
async checkAndExtractUrlContent(url, options) {
|
|
176
|
+
await this._preflightOrThrow(url);
|
|
177
|
+
return this.extractUrlContent(url, options);
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Private helpers
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Returns the correct extract path segment based on auth mode.
|
|
184
|
+
* x402 → /api/v1/x402/extract/<endpoint>
|
|
185
|
+
* apiKey → /api/v1/extract/<endpoint>
|
|
186
|
+
*
|
|
187
|
+
* @param endpoint
|
|
188
|
+
*/
|
|
189
|
+
_extractPath(endpoint) {
|
|
190
|
+
return this.config.authMode === "x402"
|
|
191
|
+
? `/api/v1/x402/extract/${endpoint}`
|
|
192
|
+
: `/api/v1/extract/${endpoint}`;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Dispatch to the correct request handler based on auth mode, then
|
|
196
|
+
* normalize the response into PaidEndpointResponse.
|
|
197
|
+
* Note: payment field is only present for x402 responses.
|
|
198
|
+
*
|
|
199
|
+
* @param requestUrl
|
|
200
|
+
* @param normalizedUrl
|
|
201
|
+
* @param label - used in error messages
|
|
202
|
+
*/
|
|
203
|
+
async _makeRequest(requestUrl, normalizedUrl, label) {
|
|
204
|
+
if (this.config.authMode === "x402") {
|
|
205
|
+
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new ExtractionFailedError(normalizedUrl, `${label} failed: ${response.status} ${response.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
const data = (await response.json());
|
|
210
|
+
return { success: data.success, results: data.results, payment };
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const { response } = await handleApiKeyRequest(requestUrl, this.config);
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
throw new ExtractionFailedError(normalizedUrl, `${label} failed: ${response.status} ${response.statusText}`);
|
|
216
|
+
}
|
|
217
|
+
const data = (await response.json());
|
|
218
|
+
// payment field intentionally omitted for apiKey auth — not applicable
|
|
219
|
+
return { success: data.success, results: data.results };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Preflight check helper — throws RobotsBlockedError if not allowed
|
|
224
|
+
*
|
|
225
|
+
* @param url
|
|
226
|
+
*/
|
|
227
|
+
async _preflightOrThrow(url) {
|
|
269
228
|
const checkResponse = await this.preflightUrlCheck(url);
|
|
270
229
|
if (!checkResponse.results[0]?.data?.allowed) {
|
|
271
230
|
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
272
231
|
}
|
|
273
|
-
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Re-throw known error types, wrapping unknowns in ExtractionFailedError
|
|
235
|
+
*
|
|
236
|
+
* @param error
|
|
237
|
+
* @param url
|
|
238
|
+
* @param label
|
|
239
|
+
*/
|
|
240
|
+
_rethrowExtraction(error, url, label) {
|
|
241
|
+
if (error instanceof InvalidUrlError ||
|
|
242
|
+
error instanceof ExtractionFailedError ||
|
|
243
|
+
error instanceof PaymentFailedError ||
|
|
244
|
+
error instanceof NetworkError ||
|
|
245
|
+
error instanceof RobotsBlockedError) {
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
throw new ExtractionFailedError(url, `${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
274
249
|
}
|
|
275
250
|
}
|
package/dist/esm/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { VALID_NETWORKS } from "./types/config.js";
|
|
2
2
|
import { ConfigurationError } from "./types/errors.js";
|
|
3
3
|
/**
|
|
4
|
-
* Default configuration values
|
|
4
|
+
* Default configuration values for x402 mode
|
|
5
5
|
* Exported for testing purposes
|
|
6
6
|
*/
|
|
7
7
|
export const DEFAULTS = {
|
|
@@ -19,32 +19,65 @@ export const DEFAULTS = {
|
|
|
19
19
|
solana: "https://explorer.solana.com/tx",
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
|
+
/** Prod API key prefix */
|
|
23
|
+
const PROD_KEY_PREFIX = "mf_prod_";
|
|
24
|
+
/** Dev API key prefix */
|
|
25
|
+
const DEV_KEY_PREFIX = "mf_dev_";
|
|
22
26
|
/**
|
|
23
|
-
* Validate & initialize client configuration
|
|
27
|
+
* Validate & initialize client configuration.
|
|
28
|
+
* Accepts either X402Config (network + privateKey) or ApiKeyConfig (apiKey).
|
|
24
29
|
*
|
|
25
30
|
* @param config
|
|
26
31
|
*/
|
|
27
32
|
export function initConfig(config) {
|
|
28
|
-
|
|
33
|
+
if ("apiKey" in config) {
|
|
34
|
+
return initApiKeyConfig(config);
|
|
35
|
+
}
|
|
36
|
+
return initX402Config(config);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize config for API key auth mode.
|
|
40
|
+
* Infers apiBaseUrl from the key prefix (mf_prod_* → prod, mf_dev_* → dev).
|
|
41
|
+
*
|
|
42
|
+
* @param config
|
|
43
|
+
*/
|
|
44
|
+
function initApiKeyConfig(config) {
|
|
45
|
+
const { apiKey } = config;
|
|
46
|
+
if (!apiKey || apiKey.trim() === "") {
|
|
47
|
+
throw new ConfigurationError("API key is required");
|
|
48
|
+
}
|
|
49
|
+
if (!apiKey.startsWith(PROD_KEY_PREFIX) && !apiKey.startsWith(DEV_KEY_PREFIX)) {
|
|
50
|
+
throw new ConfigurationError(`Invalid API key format: must start with "${PROD_KEY_PREFIX}" or "${DEV_KEY_PREFIX}"`);
|
|
51
|
+
}
|
|
52
|
+
const apiBaseUrl = apiKey.startsWith(PROD_KEY_PREFIX)
|
|
53
|
+
? "https://minifetch.com"
|
|
54
|
+
: "http://localhost:4021";
|
|
55
|
+
return {
|
|
56
|
+
authMode: "apiKey",
|
|
57
|
+
apiKey,
|
|
58
|
+
apiBaseUrl,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Initialize config for x402 crypto payment mode.
|
|
63
|
+
*
|
|
64
|
+
* @param config
|
|
65
|
+
*/
|
|
66
|
+
function initX402Config(config) {
|
|
29
67
|
if (!config.privateKey || config.privateKey.trim() === "") {
|
|
30
68
|
throw new ConfigurationError("Private key is required");
|
|
31
69
|
}
|
|
32
|
-
// Validate network
|
|
33
70
|
const network = config.network || DEFAULTS.network;
|
|
34
71
|
if (!VALID_NETWORKS.includes(network)) {
|
|
35
72
|
throw new ConfigurationError(`Invalid network: "${network}". Must be one of: ${VALID_NETWORKS.join(", ")}`);
|
|
36
73
|
}
|
|
37
|
-
// Validate private key format based on network
|
|
38
74
|
validatePrivateKey(config.privateKey, network);
|
|
39
|
-
// Build initalized config
|
|
40
|
-
const apiBaseUrl = DEFAULTS.apiBaseUrls[network];
|
|
41
|
-
const explorerUrl = DEFAULTS.explorerUrls[network];
|
|
42
|
-
// console.log(`config.ts: network: ${network} apiBaseUrl: ${apiBaseUrl}`)
|
|
43
75
|
return {
|
|
76
|
+
authMode: "x402",
|
|
44
77
|
network,
|
|
45
78
|
privateKey: config.privateKey,
|
|
46
|
-
apiBaseUrl,
|
|
47
|
-
explorerUrl,
|
|
79
|
+
apiBaseUrl: DEFAULTS.apiBaseUrls[network],
|
|
80
|
+
explorerUrl: DEFAULTS.explorerUrls[network],
|
|
48
81
|
};
|
|
49
82
|
}
|
|
50
83
|
/**
|
|
@@ -55,13 +88,11 @@ export function initConfig(config) {
|
|
|
55
88
|
*/
|
|
56
89
|
function validatePrivateKey(privateKey, network) {
|
|
57
90
|
if (network === "solana" || network === "solana-devnet") {
|
|
58
|
-
// Solana: base58 encoded, typically 88 characters
|
|
59
91
|
if (!/^[1-9A-HJ-NP-Za-km-z]{87,88}$/.test(privateKey)) {
|
|
60
92
|
throw new ConfigurationError("Invalid Solana private key format (expected base58 string)");
|
|
61
93
|
}
|
|
62
94
|
}
|
|
63
95
|
else {
|
|
64
|
-
// EVM: hex string, 64 characters (with or without 0x prefix)
|
|
65
96
|
const cleanKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
|
|
66
97
|
if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
|
|
67
98
|
throw new ConfigurationError("Invalid EVM private key format (expected hex string that starts with 0x)");
|
|
@@ -16,50 +16,36 @@ import { PaymentFailedError, NetworkError } from "../types/errors.js";
|
|
|
16
16
|
* @param config
|
|
17
17
|
*/
|
|
18
18
|
export async function handlePayment(url, config) {
|
|
19
|
-
if (!config.privateKey) {
|
|
20
|
-
throw new PaymentFailedError("Private key required for payments");
|
|
21
|
-
}
|
|
22
19
|
try {
|
|
23
|
-
// Create x402 client and signer based on network type
|
|
24
20
|
const _x402Client = new x402Client();
|
|
25
21
|
let payer;
|
|
26
|
-
const isEvm = config.network
|
|
27
|
-
const isSolana = config.network
|
|
22
|
+
const isEvm = config.network?.startsWith("base");
|
|
23
|
+
const isSolana = config.network?.startsWith("solana");
|
|
28
24
|
if (isEvm) {
|
|
29
|
-
// Initialize EVM signer and register scheme
|
|
30
25
|
const signer = privateKeyToAccount(config.privateKey);
|
|
31
|
-
const evmSigner = signer;
|
|
26
|
+
const evmSigner = signer;
|
|
32
27
|
registerExactEvmScheme(_x402Client, { signer: evmSigner });
|
|
33
28
|
payer = signer.address;
|
|
34
29
|
}
|
|
35
30
|
else if (isSolana) {
|
|
36
|
-
|
|
31
|
+
if (!config.privateKey)
|
|
32
|
+
throw new PaymentFailedError('privateKey is required for Solana payments');
|
|
37
33
|
const privateKeyBytes = bs58.decode(config.privateKey);
|
|
38
34
|
const signer = await createKeyPairSignerFromBytes(privateKeyBytes);
|
|
39
|
-
const svmSigner = signer;
|
|
35
|
+
const svmSigner = signer;
|
|
40
36
|
registerExactSvmScheme(_x402Client, { signer: svmSigner });
|
|
41
37
|
payer = signer.address;
|
|
42
38
|
}
|
|
43
39
|
else {
|
|
44
40
|
throw new PaymentFailedError(`Unsupported network: ${config.network}`);
|
|
45
41
|
}
|
|
46
|
-
// Wrap fetch with payment capabilities
|
|
47
42
|
const fetchWithPayment = wrapFetchWithPayment(fetch, _x402Client);
|
|
48
|
-
|
|
49
|
-
// console.log("Attempting to fetch w payment:", url);
|
|
50
|
-
const response = await fetchWithPayment(url, {
|
|
51
|
-
method: "GET",
|
|
52
|
-
});
|
|
43
|
+
const response = await fetchWithPayment(url, { method: "GET" });
|
|
53
44
|
if (!response.ok) {
|
|
54
45
|
throw new NetworkError(`Request failed: ${response.status} ${response.statusText}`);
|
|
55
46
|
}
|
|
56
|
-
// Extract payment receipt from response headers
|
|
57
47
|
const httpClient = new x402HTTPClient(_x402Client);
|
|
58
48
|
const paymentResponse = httpClient.getPaymentSettleResponse(name => response.headers.get(name));
|
|
59
|
-
// DEBUG:
|
|
60
|
-
// console.log("Payment response:");
|
|
61
|
-
// console.log(paymentResponse);
|
|
62
|
-
// Build payment info for user
|
|
63
49
|
const payment = {
|
|
64
50
|
success: true,
|
|
65
51
|
payer,
|
|
@@ -78,8 +64,27 @@ export async function handlePayment(url, config) {
|
|
|
78
64
|
throw new PaymentFailedError(`Payment failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
79
65
|
}
|
|
80
66
|
}
|
|
81
|
-
// Helper fn to build explorer links
|
|
82
67
|
/**
|
|
68
|
+
* Handle API key auth flow — simple Bearer token request, no crypto.
|
|
69
|
+
* No payment info is returned (not applicable for this auth mode).
|
|
70
|
+
*
|
|
71
|
+
* @param url
|
|
72
|
+
* @param config
|
|
73
|
+
*/
|
|
74
|
+
export async function handleApiKeyRequest(url, config) {
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new NetworkError(`Request failed: ${response.status} ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
return { response };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build block explorer link for a transaction hash
|
|
83
88
|
*
|
|
84
89
|
* @param config
|
|
85
90
|
* @param txHash
|
package/dist/types/client.d.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import type { ClientConfig } from "./types/config.js";
|
|
2
2
|
import type { PreflightCheckResponse, PaidEndpointResponse } from "./types/responses.js";
|
|
3
3
|
/**
|
|
4
|
-
* Main Minifetch API client
|
|
5
|
-
*
|
|
4
|
+
* Main Minifetch API client.
|
|
5
|
+
* Supports two auth modes:
|
|
6
|
+
* - x402: crypto micropayments via Coinbase x402 (pass network + privateKey)
|
|
7
|
+
* - apiKey: Stripe-backed credits (pass apiKey: "mf_prod_..." or "mf_dev_...")
|
|
6
8
|
*/
|
|
7
9
|
export declare class MinifetchClient {
|
|
8
10
|
private config;
|
|
9
11
|
private baseUrl;
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param config
|
|
13
|
+
* @param config - Either { network, privateKey } for x402 or { apiKey } for API key auth
|
|
13
14
|
*/
|
|
14
15
|
constructor(config: ClientConfig);
|
|
15
16
|
/**
|
|
16
|
-
* Check if URL is allowed by robots.txt (free preflight check)
|
|
17
|
+
* Check if URL is allowed by robots.txt (free preflight check — no auth required)
|
|
17
18
|
*
|
|
18
19
|
* @param url
|
|
19
20
|
* @throws {InvalidUrlError} if URL is invalid
|
|
@@ -21,15 +22,15 @@ export declare class MinifetchClient {
|
|
|
21
22
|
*/
|
|
22
23
|
preflightUrlCheck(url: string): Promise<PreflightCheckResponse>;
|
|
23
24
|
/**
|
|
24
|
-
* Extract URL metadata (paid
|
|
25
|
+
* Extract URL metadata (paid endpoint)
|
|
25
26
|
*
|
|
26
27
|
* @param url
|
|
27
28
|
* @param options
|
|
29
|
+
* @param options.verbosity - "standard" (default) or "full"
|
|
28
30
|
* @param options.includeResponseBody
|
|
29
|
-
* @param options.verbosity - Controls response detail level: "standard" (default) or "full"
|
|
30
31
|
* @throws {InvalidUrlError} if URL is invalid
|
|
31
32
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
32
|
-
* @throws {PaymentFailedError} if payment fails
|
|
33
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
33
34
|
* @throws {NetworkError} various reasons, check README
|
|
34
35
|
*/
|
|
35
36
|
extractUrlMetadata(url: string, options?: {
|
|
@@ -37,69 +38,69 @@ export declare class MinifetchClient {
|
|
|
37
38
|
includeResponseBody?: boolean;
|
|
38
39
|
}): Promise<PaidEndpointResponse>;
|
|
39
40
|
/**
|
|
40
|
-
* Extract URL links (paid
|
|
41
|
+
* Extract URL links (paid endpoint)
|
|
41
42
|
*
|
|
42
43
|
* @param url
|
|
43
44
|
* @throws {InvalidUrlError} if URL is invalid
|
|
44
45
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
45
|
-
* @throws {PaymentFailedError} if payment fails
|
|
46
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
46
47
|
* @throws {NetworkError} various reasons, check README
|
|
47
48
|
*/
|
|
48
49
|
extractUrlLinks(url: string): Promise<PaidEndpointResponse>;
|
|
49
50
|
/**
|
|
50
|
-
* Extract URL preview (paid
|
|
51
|
+
* Extract URL preview (paid endpoint)
|
|
51
52
|
*
|
|
52
53
|
* @param url
|
|
53
54
|
* @throws {InvalidUrlError} if URL is invalid
|
|
54
55
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
55
|
-
* @throws {PaymentFailedError} if payment fails
|
|
56
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
56
57
|
* @throws {NetworkError} various reasons, check README
|
|
57
58
|
*/
|
|
58
59
|
extractUrlPreview(url: string): Promise<PaidEndpointResponse>;
|
|
59
60
|
/**
|
|
60
|
-
* Extract URL content as markdown (paid
|
|
61
|
+
* Extract URL content as markdown (paid endpoint)
|
|
61
62
|
*
|
|
62
63
|
* @param url
|
|
63
64
|
* @param options
|
|
64
65
|
* @param options.includeMediaUrls
|
|
65
66
|
* @throws {InvalidUrlError} if URL is invalid
|
|
66
67
|
* @throws {ExtractionFailedError} various reasons, check README
|
|
67
|
-
* @throws {PaymentFailedError} if payment fails
|
|
68
|
+
* @throws {PaymentFailedError} if x402 payment fails
|
|
68
69
|
* @throws {NetworkError} various reasons, check README
|
|
69
70
|
*/
|
|
70
71
|
extractUrlContent(url: string, options?: {
|
|
71
72
|
includeMediaUrls?: boolean;
|
|
72
73
|
}): Promise<PaidEndpointResponse>;
|
|
73
74
|
/**
|
|
74
|
-
* Check URL
|
|
75
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
75
|
+
* Check URL then extract metadata in one call.
|
|
76
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
76
77
|
*
|
|
77
78
|
* @param url
|
|
78
79
|
* @param options
|
|
79
|
-
* @param options.verbosity -
|
|
80
|
-
* @param options.includeResponseBody
|
|
80
|
+
* @param options.verbosity - "standard" (default) or "full"
|
|
81
|
+
* @param options.includeResponseBody
|
|
81
82
|
*/
|
|
82
83
|
checkAndExtractUrlMetadata(url: string, options?: {
|
|
83
84
|
verbosity?: "standard" | "full";
|
|
84
85
|
includeResponseBody?: boolean;
|
|
85
86
|
}): Promise<PaidEndpointResponse>;
|
|
86
87
|
/**
|
|
87
|
-
* Check URL
|
|
88
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
88
|
+
* Check URL then extract links in one call.
|
|
89
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
89
90
|
*
|
|
90
91
|
* @param url
|
|
91
92
|
*/
|
|
92
93
|
checkAndExtractUrlLinks(url: string): Promise<PaidEndpointResponse>;
|
|
93
94
|
/**
|
|
94
|
-
* Check URL
|
|
95
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
95
|
+
* Check URL then extract preview in one call.
|
|
96
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
96
97
|
*
|
|
97
98
|
* @param url
|
|
98
99
|
*/
|
|
99
100
|
checkAndExtractUrlPreview(url: string): Promise<PaidEndpointResponse>;
|
|
100
101
|
/**
|
|
101
|
-
* Check URL
|
|
102
|
-
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
102
|
+
* Check URL then extract content in one call.
|
|
103
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL.
|
|
103
104
|
*
|
|
104
105
|
* @param url
|
|
105
106
|
* @param options
|
|
@@ -108,4 +109,36 @@ export declare class MinifetchClient {
|
|
|
108
109
|
checkAndExtractUrlContent(url: string, options?: {
|
|
109
110
|
includeMediaUrls?: boolean;
|
|
110
111
|
}): Promise<PaidEndpointResponse>;
|
|
112
|
+
/**
|
|
113
|
+
* Returns the correct extract path segment based on auth mode.
|
|
114
|
+
* x402 → /api/v1/x402/extract/<endpoint>
|
|
115
|
+
* apiKey → /api/v1/extract/<endpoint>
|
|
116
|
+
*
|
|
117
|
+
* @param endpoint
|
|
118
|
+
*/
|
|
119
|
+
private _extractPath;
|
|
120
|
+
/**
|
|
121
|
+
* Dispatch to the correct request handler based on auth mode, then
|
|
122
|
+
* normalize the response into PaidEndpointResponse.
|
|
123
|
+
* Note: payment field is only present for x402 responses.
|
|
124
|
+
*
|
|
125
|
+
* @param requestUrl
|
|
126
|
+
* @param normalizedUrl
|
|
127
|
+
* @param label - used in error messages
|
|
128
|
+
*/
|
|
129
|
+
private _makeRequest;
|
|
130
|
+
/**
|
|
131
|
+
* Preflight check helper — throws RobotsBlockedError if not allowed
|
|
132
|
+
*
|
|
133
|
+
* @param url
|
|
134
|
+
*/
|
|
135
|
+
private _preflightOrThrow;
|
|
136
|
+
/**
|
|
137
|
+
* Re-throw known error types, wrapping unknowns in ExtractionFailedError
|
|
138
|
+
*
|
|
139
|
+
* @param error
|
|
140
|
+
* @param url
|
|
141
|
+
* @param label
|
|
142
|
+
*/
|
|
143
|
+
private _rethrowExtraction;
|
|
111
144
|
}
|
package/dist/types/init.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClientConfig, InitializedConfig, Network } from "./types/config.js";
|
|
2
2
|
/**
|
|
3
|
-
* Default configuration values
|
|
3
|
+
* Default configuration values for x402 mode
|
|
4
4
|
* Exported for testing purposes
|
|
5
5
|
*/
|
|
6
6
|
export declare const DEFAULTS: {
|
|
@@ -19,7 +19,8 @@ export declare const DEFAULTS: {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
|
-
* Validate & initialize client configuration
|
|
22
|
+
* Validate & initialize client configuration.
|
|
23
|
+
* Accepts either X402Config (network + privateKey) or ApiKeyConfig (apiKey).
|
|
23
24
|
*
|
|
24
25
|
* @param config
|
|
25
26
|
*/
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
export declare const VALID_NETWORKS: readonly ["base", "base-sepolia", "solana", "solana-devnet"];
|
|
2
2
|
export type Network = (typeof VALID_NETWORKS)[number];
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
3
|
+
/**
|
|
4
|
+
* Config for x402 crypto micropayment auth
|
|
5
|
+
*/
|
|
6
|
+
export interface X402Config {
|
|
7
|
+
/** Blockchain network to use for payments @default 'base' */
|
|
9
8
|
network: Network;
|
|
10
|
-
/**
|
|
11
|
-
* Private key for signing transactions -
|
|
12
|
-
* hex string for EVM, base58 for Solana
|
|
13
|
-
*/
|
|
9
|
+
/** Private key for signing transactions — hex for EVM, base58 for Solana */
|
|
14
10
|
privateKey: string;
|
|
15
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Config for Stripe-backed API key auth.
|
|
14
|
+
* apiBaseUrl is inferred from the key prefix:
|
|
15
|
+
* mf_prod_* → https://minifetch.com
|
|
16
|
+
* mf_dev_* → http://localhost:4021
|
|
17
|
+
*/
|
|
18
|
+
export interface ApiKeyConfig {
|
|
19
|
+
apiKey: string;
|
|
20
|
+
}
|
|
21
|
+
export type ClientConfig = X402Config | ApiKeyConfig;
|
|
22
|
+
/** Discriminant for internal use after init */
|
|
23
|
+
export type AuthMode = "x402" | "apiKey";
|
|
16
24
|
export interface InitializedConfig {
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
authMode: AuthMode;
|
|
26
|
+
network?: Network;
|
|
27
|
+
privateKey?: string;
|
|
28
|
+
explorerUrl?: string;
|
|
29
|
+
apiKey?: string;
|
|
19
30
|
apiBaseUrl: string;
|
|
20
|
-
explorerUrl: string;
|
|
21
31
|
}
|
|
@@ -29,12 +29,12 @@ export interface PaidEndpointResponse {
|
|
|
29
29
|
}>;
|
|
30
30
|
/**
|
|
31
31
|
* Payment information
|
|
32
|
-
* Only present for paid endpoints when request was successful
|
|
32
|
+
* Only present for paid x402 endpoints when request was successful
|
|
33
33
|
* */
|
|
34
34
|
payment?: PaymentInfo;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Payment information included with successful paid API responses
|
|
37
|
+
* Payment information included with successful paid x402 API responses
|
|
38
38
|
*/
|
|
39
39
|
export interface PaymentInfo {
|
|
40
40
|
/** Whether the payment was successful **/
|
|
@@ -14,3 +14,13 @@ export declare function handlePayment(url: string, config: InitializedConfig): P
|
|
|
14
14
|
response: Response;
|
|
15
15
|
payment?: PaymentInfo;
|
|
16
16
|
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Handle API key auth flow — simple Bearer token request, no crypto.
|
|
19
|
+
* No payment info is returned (not applicable for this auth mode).
|
|
20
|
+
*
|
|
21
|
+
* @param url
|
|
22
|
+
* @param config
|
|
23
|
+
*/
|
|
24
|
+
export declare function handleApiKeyRequest(url: string, config: InitializedConfig): Promise<{
|
|
25
|
+
response: Response;
|
|
26
|
+
}>;
|