apinow-sdk 0.14.0 → 0.15.1
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 +50 -103
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +219 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# apinow-sdk
|
|
2
2
|
|
|
3
|
-
Pay-per-call API SDK for [APINow.fun](https://apinow.fun) — wraps [x402](https://www.x402.org/) so you don't have to.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
3
|
+
Pay-per-call API SDK & CLI for [APINow.fun](https://apinow.fun) — wraps [x402](https://www.x402.org/) so you don't have to.
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
npm install apinow-sdk
|
|
9
7
|
```
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## SDK
|
|
12
10
|
|
|
13
11
|
```typescript
|
|
14
12
|
import { createClient } from 'apinow-sdk';
|
|
@@ -17,133 +15,82 @@ const apinow = createClient({
|
|
|
17
15
|
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
|
|
18
16
|
});
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
// call any endpoint — x402 payment is handled automatically
|
|
19
|
+
const data = await apinow.call('/api/endpoints/apinowfun/translate', {
|
|
21
20
|
method: 'POST',
|
|
22
|
-
body: { text: 'Hello world',
|
|
21
|
+
body: { text: 'Hello world', targetLanguage: 'es' },
|
|
23
22
|
});
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
That's it. If the endpoint requires payment, the SDK handles the full [x402 payment flow](https://www.x402.org/) automatically — no manual token handling, no extra steps.
|
|
29
|
-
|
|
30
|
-
## How It Works
|
|
31
|
-
|
|
32
|
-
1. You call an APINow endpoint via `apinow.call()`
|
|
33
|
-
2. If the server responds with `402 Payment Required`, the underlying `@x402/fetch` layer intercepts it
|
|
34
|
-
3. Payment is signed and submitted using your wallet
|
|
35
|
-
4. The original request is retried with proof of payment
|
|
36
|
-
5. You get the API response back
|
|
37
|
-
|
|
38
|
-
## API
|
|
39
|
-
|
|
40
|
-
### `createClient(config)`
|
|
41
|
-
|
|
42
|
-
Creates an SDK client instance.
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
import { createClient } from 'apinow-sdk';
|
|
46
|
-
// or
|
|
47
|
-
import createClient from 'apinow-sdk';
|
|
24
|
+
// semantic search
|
|
25
|
+
const results = await apinow.search('weather forecast', 5);
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
baseUrl: 'https://apinow.fun', // optional — defaults to https://apinow.fun
|
|
52
|
-
});
|
|
27
|
+
// free endpoint info (cost, schema, wallet)
|
|
28
|
+
const info = await apinow.info('gg402', 'horoscope');
|
|
53
29
|
```
|
|
54
30
|
|
|
55
|
-
|
|
31
|
+
### `createClient(config)`
|
|
56
32
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `search()` | Semantic search across all APINow endpoints |
|
|
62
|
-
| `info()` | Get public endpoint info (free, no payment) |
|
|
63
|
-
| `fetch` | The underlying x402-wrapped `fetch` for advanced use |
|
|
33
|
+
| Option | Type | Default | Description |
|
|
34
|
+
|--------|------|---------|-------------|
|
|
35
|
+
| `privateKey` | `` `0x${string}` `` | — | EVM private key (required) |
|
|
36
|
+
| `baseUrl` | `string` | `https://apinow.fun` | API base URL |
|
|
64
37
|
|
|
65
|
-
|
|
38
|
+
Returns:
|
|
66
39
|
|
|
67
|
-
|
|
40
|
+
| Method | Description |
|
|
41
|
+
|--------|-------------|
|
|
42
|
+
| `call(endpoint, opts?)` | Call any endpoint with automatic x402 payment |
|
|
43
|
+
| `search(query, limit?)` | Semantic search across all endpoints |
|
|
44
|
+
| `info(ns, name)` | Get endpoint details (free) |
|
|
45
|
+
| `wallet` | Your wallet address |
|
|
46
|
+
| `fetch` | Raw x402-wrapped `fetch` for advanced use |
|
|
68
47
|
|
|
69
|
-
|
|
48
|
+
## CLI
|
|
70
49
|
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
method: 'POST',
|
|
74
|
-
body: { text: 'Hello world', selectedLanguage: 'es' },
|
|
75
|
-
});
|
|
50
|
+
```bash
|
|
51
|
+
npx apinow <command>
|
|
76
52
|
```
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| Param | Type | Default | Description |
|
|
81
|
-
|-------|------|---------|-------------|
|
|
82
|
-
| `endpoint` | `string` | — | Full URL or path (paths are resolved against `baseUrl`) |
|
|
83
|
-
| `opts.method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE'` | `'POST'` | HTTP method |
|
|
84
|
-
| `opts.body` | `Record<string, any>` | — | Request body (ignored for GET) |
|
|
85
|
-
| `opts.headers` | `Record<string, string>` | — | Additional headers |
|
|
54
|
+
### `search` — find endpoints
|
|
86
55
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
### `apinow.search(query, limit?)`
|
|
92
|
-
|
|
93
|
-
Semantic search across all APINow endpoints.
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
const results = await apinow.search('translate text', 5);
|
|
56
|
+
```bash
|
|
57
|
+
npx apinow search "weather api" --limit 5
|
|
97
58
|
```
|
|
98
59
|
|
|
99
|
-
|
|
100
|
-
|-------|------|---------|-------------|
|
|
101
|
-
| `query` | `string` | — | Search query |
|
|
102
|
-
| `limit` | `number` | `10` | Max results |
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
### `apinow.info(namespace, endpointName)`
|
|
60
|
+
### `list` — browse endpoints
|
|
107
61
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
62
|
+
```bash
|
|
63
|
+
npx apinow list # popular endpoints
|
|
64
|
+
npx apinow list --sort newest --limit 10
|
|
65
|
+
npx apinow list --namespace gg402
|
|
66
|
+
npx apinow list --search translate
|
|
112
67
|
```
|
|
113
68
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
### `apinow.fetch`
|
|
69
|
+
### `info` — endpoint details
|
|
117
70
|
|
|
118
|
-
|
|
71
|
+
Shows cost, wallet, chain, input/output schemas, and examples.
|
|
119
72
|
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: { 'Content-Type': 'application/json' },
|
|
124
|
-
body: JSON.stringify({ key: 'value' }),
|
|
125
|
-
});
|
|
73
|
+
```bash
|
|
74
|
+
npx apinow info gg402/horoscope
|
|
126
75
|
```
|
|
127
76
|
|
|
128
|
-
|
|
77
|
+
### `call` — call an endpoint (paid)
|
|
129
78
|
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
baseUrl?: string;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
interface CallOptions {
|
|
137
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
138
|
-
body?: Record<string, any>;
|
|
139
|
-
headers?: Record<string, string>;
|
|
140
|
-
}
|
|
79
|
+
```bash
|
|
80
|
+
APINOW_WALLET_PKEY=0x... npx apinow call gg402/horoscope -d '{"sign":"aries"}'
|
|
81
|
+
npx apinow call ns/endpoint -m GET -k 0xYOUR_KEY
|
|
141
82
|
```
|
|
142
83
|
|
|
84
|
+
| Flag | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `-d, --data <json>` | JSON request body |
|
|
87
|
+
| `-m, --method <method>` | HTTP method (default: from endpoint) |
|
|
88
|
+
| `-k, --key <privateKey>` | Wallet key (or set `APINOW_WALLET_PKEY`) |
|
|
89
|
+
|
|
143
90
|
## Requirements
|
|
144
91
|
|
|
145
|
-
- Node.js v18+
|
|
146
|
-
-
|
|
92
|
+
- Node.js v18+
|
|
93
|
+
- EVM wallet with funds on Base for paid endpoints
|
|
147
94
|
|
|
148
95
|
## License
|
|
149
96
|
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createClient } from './index.js';
|
|
4
|
+
const API_BASE = 'https://apinow.fun';
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name('apinow')
|
|
8
|
+
.description('CLI for APINow.fun — search, inspect, and call pay-per-request APIs')
|
|
9
|
+
.version('0.15.0');
|
|
10
|
+
// ─── Helpers ───
|
|
11
|
+
function getPrivateKey(opts) {
|
|
12
|
+
const raw = opts.key || process.env.APINOW_WALLET_PKEY || process.env.PRIVATE_KEY;
|
|
13
|
+
if (!raw) {
|
|
14
|
+
console.error('Error: Private key required. Set APINOW_WALLET_PKEY or pass --key');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
return (raw.startsWith('0x') ? raw : `0x${raw}`);
|
|
18
|
+
}
|
|
19
|
+
async function fetchJson(url, init) {
|
|
20
|
+
const res = await fetch(url, init);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const body = await res.text();
|
|
23
|
+
throw new Error(`${res.status} ${res.statusText}: ${body}`);
|
|
24
|
+
}
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
function formatUsd(paymentOptions) {
|
|
28
|
+
if (!paymentOptions?.length)
|
|
29
|
+
return 'free';
|
|
30
|
+
const p = paymentOptions[0];
|
|
31
|
+
const usd = p.usdAmount ?? p.amount ?? 0;
|
|
32
|
+
return `$${Number(usd).toFixed(4)}`;
|
|
33
|
+
}
|
|
34
|
+
function printTable(rows) {
|
|
35
|
+
if (!rows.length)
|
|
36
|
+
return;
|
|
37
|
+
const widths = rows[0].map((_, col) => Math.max(...rows.map((r) => (r[col] || '').length)));
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
console.log(row.map((cell, i) => (cell || '').padEnd(widths[i])).join(' '));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function truncate(s, max) {
|
|
43
|
+
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
44
|
+
}
|
|
45
|
+
// ─── search ───
|
|
46
|
+
program
|
|
47
|
+
.command('search <query>')
|
|
48
|
+
.description('Semantic search across all endpoints')
|
|
49
|
+
.option('-l, --limit <n>', 'Max results', '10')
|
|
50
|
+
.action(async (query, opts) => {
|
|
51
|
+
try {
|
|
52
|
+
const data = await fetchJson(`${API_BASE}/api/endpoints/semantic-search`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify({ query, limit: Number(opts.limit) }),
|
|
56
|
+
});
|
|
57
|
+
const endpoints = data.endpoints || data.results || data;
|
|
58
|
+
if (!Array.isArray(endpoints) || !endpoints.length) {
|
|
59
|
+
console.log('No results found.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const rows = [['ENDPOINT', 'METHOD', 'COST', 'DESCRIPTION']];
|
|
63
|
+
for (const ep of endpoints) {
|
|
64
|
+
const key = `${ep.namespace}/${ep.endpointName}`;
|
|
65
|
+
rows.push([
|
|
66
|
+
key,
|
|
67
|
+
ep.httpMethod || 'POST',
|
|
68
|
+
formatUsd(ep.paymentOptions),
|
|
69
|
+
truncate(ep.description || '', 60),
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
printTable(rows);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(`Error: ${err.message}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// ─── list ───
|
|
80
|
+
program
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('List available endpoints')
|
|
83
|
+
.option('-l, --limit <n>', 'Max results', '20')
|
|
84
|
+
.option('-s, --sort <sortBy>', 'Sort by: popular | newest', 'popular')
|
|
85
|
+
.option('-n, --namespace <ns>', 'Filter by namespace')
|
|
86
|
+
.option('-q, --search <term>', 'Text search filter')
|
|
87
|
+
.action(async (opts) => {
|
|
88
|
+
try {
|
|
89
|
+
const params = new URLSearchParams({
|
|
90
|
+
limit: opts.limit,
|
|
91
|
+
sortBy: opts.sort,
|
|
92
|
+
});
|
|
93
|
+
if (opts.namespace)
|
|
94
|
+
params.set('namespace', opts.namespace);
|
|
95
|
+
if (opts.search)
|
|
96
|
+
params.set('search', opts.search);
|
|
97
|
+
const data = await fetchJson(`${API_BASE}/api/endpoints?${params}`);
|
|
98
|
+
const endpoints = data.endpoints || [];
|
|
99
|
+
if (!endpoints.length) {
|
|
100
|
+
console.log('No endpoints found.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const rows = [['ENDPOINT', 'METHOD', 'COST', 'CALLS', 'DESCRIPTION']];
|
|
104
|
+
for (const ep of endpoints) {
|
|
105
|
+
rows.push([
|
|
106
|
+
`${ep.namespace}/${ep.endpointName}`,
|
|
107
|
+
ep.httpMethod || 'POST',
|
|
108
|
+
formatUsd(ep.paymentOptions),
|
|
109
|
+
String(ep.callCount || 0),
|
|
110
|
+
truncate(ep.description || '', 50),
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
printTable(rows);
|
|
114
|
+
if (data.hasMore) {
|
|
115
|
+
console.log(`\n… more results available (showing ${endpoints.length})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(`Error: ${err.message}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// ─── info ───
|
|
124
|
+
program
|
|
125
|
+
.command('info <endpoint>')
|
|
126
|
+
.description('Get endpoint details (cost, wallet, schemas, examples)')
|
|
127
|
+
.action(async (endpoint) => {
|
|
128
|
+
try {
|
|
129
|
+
if (!endpoint.includes('/')) {
|
|
130
|
+
throw new Error('Format: namespace/endpoint-name');
|
|
131
|
+
}
|
|
132
|
+
const [ns, ep] = endpoint.split('/');
|
|
133
|
+
const data = await fetchJson(`${API_BASE}/api/endpoints/${ns}/${ep}/details`);
|
|
134
|
+
const cost = formatUsd(data.paymentOptions);
|
|
135
|
+
const payment = data.paymentOptions?.[0];
|
|
136
|
+
console.log(`\n ${data.namespace}/${data.endpointName}`);
|
|
137
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
138
|
+
console.log(` ${data.description || '(no description)'}\n`);
|
|
139
|
+
console.log(` Method: ${data.httpMethod || 'POST'}`);
|
|
140
|
+
console.log(` Cost: ${cost}`);
|
|
141
|
+
console.log(` Chain: ${data.chain || 'base'}`);
|
|
142
|
+
console.log(` Wallet: ${data.walletAddress || '—'}`);
|
|
143
|
+
if (data.model)
|
|
144
|
+
console.log(` Model: ${data.model}`);
|
|
145
|
+
if (data.tags?.length)
|
|
146
|
+
console.log(` Tags: ${data.tags.join(', ')}`);
|
|
147
|
+
if (data.docsUrl)
|
|
148
|
+
console.log(` Docs: ${data.docsUrl}`);
|
|
149
|
+
if (payment) {
|
|
150
|
+
console.log(`\n Payment:`);
|
|
151
|
+
console.log(` Network: ${payment.network || 'base-sepolia'}`);
|
|
152
|
+
console.log(` Token: ${payment.resource || payment.token || 'USDC'}`);
|
|
153
|
+
console.log(` Amount: ${payment.amount ?? '—'}`);
|
|
154
|
+
if (payment.usdAmount != null)
|
|
155
|
+
console.log(` USD: $${payment.usdAmount}`);
|
|
156
|
+
}
|
|
157
|
+
if (data.querySchema) {
|
|
158
|
+
console.log(`\n Input Schema:`);
|
|
159
|
+
const schema = data.querySchema;
|
|
160
|
+
if (schema.properties) {
|
|
161
|
+
const required = new Set(schema.required || []);
|
|
162
|
+
for (const [key, val] of Object.entries(schema.properties)) {
|
|
163
|
+
const req = required.has(key) ? ' (required)' : '';
|
|
164
|
+
const desc = val.description ? ` — ${val.description}` : '';
|
|
165
|
+
const ex = val.example ? ` e.g. ${JSON.stringify(val.example)}` : '';
|
|
166
|
+
console.log(` ${key}: ${val.type || 'any'}${req}${desc}${ex}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.log(` ${JSON.stringify(schema, null, 4).replace(/\n/g, '\n ')}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (data.responseSchema) {
|
|
174
|
+
console.log(`\n Output Schema:`);
|
|
175
|
+
console.log(` ${JSON.stringify(data.responseSchema, null, 4).replace(/\n/g, '\n ')}`);
|
|
176
|
+
}
|
|
177
|
+
if (data.exampleQuery) {
|
|
178
|
+
console.log(`\n Example Input:`);
|
|
179
|
+
console.log(` ${JSON.stringify(data.exampleQuery, null, 2).replace(/\n/g, '\n ')}`);
|
|
180
|
+
}
|
|
181
|
+
console.log('');
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error(`Error: ${err.message}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// ─── call ───
|
|
189
|
+
program
|
|
190
|
+
.command('call <endpoint>')
|
|
191
|
+
.description('Call an endpoint (x402 paid)')
|
|
192
|
+
.option('-d, --data <json>', 'JSON body')
|
|
193
|
+
.option('-m, --method <method>', 'HTTP method')
|
|
194
|
+
.option('-k, --key <privateKey>', 'Wallet private key')
|
|
195
|
+
.action(async (endpoint, opts) => {
|
|
196
|
+
try {
|
|
197
|
+
if (!endpoint.includes('/')) {
|
|
198
|
+
throw new Error('Format: namespace/endpoint-name');
|
|
199
|
+
}
|
|
200
|
+
const privateKey = getPrivateKey(opts);
|
|
201
|
+
const [ns, ep] = endpoint.split('/');
|
|
202
|
+
const details = await fetchJson(`${API_BASE}/api/endpoints/${ns}/${ep}/details`);
|
|
203
|
+
const method = (opts.method || details?.httpMethod || 'POST').toUpperCase();
|
|
204
|
+
const body = opts.data ? JSON.parse(opts.data) : undefined;
|
|
205
|
+
const cost = formatUsd(details?.paymentOptions);
|
|
206
|
+
console.error(`Calling ${ns}/${ep} [${method}] — cost: ${cost}`);
|
|
207
|
+
const apinow = createClient({ privateKey });
|
|
208
|
+
const result = await apinow.call(`/api/endpoints/${ns}/${ep}`, {
|
|
209
|
+
method: method,
|
|
210
|
+
...(body ? { body } : {}),
|
|
211
|
+
});
|
|
212
|
+
console.log(JSON.stringify(result, null, 2));
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error(`Error: ${err.message}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apinow-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pay-per-call API SDK for APINow.fun — wraps x402 so you don't have to",
|
|
3
|
+
"version": "0.15.1",
|
|
4
|
+
"description": "Pay-per-call API SDK & CLI for APINow.fun — wraps x402 so you don't have to",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"apinow": "dist/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
12
|
"dist"
|
|
10
13
|
],
|
|
@@ -20,7 +23,8 @@
|
|
|
20
23
|
"base",
|
|
21
24
|
"web3",
|
|
22
25
|
"ai",
|
|
23
|
-
"llm"
|
|
26
|
+
"llm",
|
|
27
|
+
"cli"
|
|
24
28
|
],
|
|
25
29
|
"author": "ApiNow.fun",
|
|
26
30
|
"license": "MIT",
|
|
@@ -30,8 +34,9 @@
|
|
|
30
34
|
},
|
|
31
35
|
"homepage": "https://apinow.fun",
|
|
32
36
|
"dependencies": {
|
|
33
|
-
"@x402/fetch": "^2.3.0",
|
|
34
37
|
"@x402/evm": "^2.3.1",
|
|
38
|
+
"@x402/fetch": "^2.3.0",
|
|
39
|
+
"commander": "^13.1.0",
|
|
35
40
|
"viem": "^2.38.3"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|