apinow-sdk 0.26.0 → 0.28.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/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ const program = new Command();
7
7
  program
8
8
  .name('apinow')
9
9
  .description('CLI for APINow.fun — search, inspect, and call pay-per-request APIs')
10
- .version('0.26.0');
10
+ .version('0.27.0');
11
11
  // ─── Helpers ───
12
12
  function getPrivateKey(opts) {
13
13
  const raw = opts.key || process.env.APINOW_WALLET_PKEY || process.env.PRIVATE_KEY;
@@ -210,6 +210,7 @@ program
210
210
  .requiredOption('--description <desc>', 'Description')
211
211
  .option('--method <method>', 'HTTP method', 'POST')
212
212
  .option('--price <usdc>', 'USDC price per call', '0.01')
213
+ .option('--splits <json>', 'Splits JSON array [{address, basisPoints, label?, tokenAddress?}] — basisPoints must sum to 10000')
213
214
  .option('-k, --key <privateKey>', 'Wallet private key')
214
215
  .action(async (opts) => {
215
216
  try {
@@ -222,6 +223,8 @@ program
222
223
  httpMethod: opts.method.toUpperCase(),
223
224
  paymentOptions: [{ usdAmount: opts.price, amount: opts.price }],
224
225
  };
226
+ if (opts.splits)
227
+ body.splits = JSON.parse(opts.splits);
225
228
  const data = await fetchJson(`${API_BASE}/api/endpoints`, {
226
229
  method: 'POST',
227
230
  headers: await walletHeaders(privateKey),
@@ -242,6 +245,7 @@ program
242
245
  .option('--url <url>', 'New URL')
243
246
  .option('--price <usdc>', 'New USDC price')
244
247
  .option('--status <status>', 'New status')
248
+ .option('--splits <json>', 'Splits JSON array — changing splits redeploys the splitter contract (V2 is immutable)')
245
249
  .option('-k, --key <privateKey>', 'Wallet private key')
246
250
  .action(async (id, opts) => {
247
251
  try {
@@ -255,6 +259,8 @@ program
255
259
  body.status = opts.status;
256
260
  if (opts.price)
257
261
  body.paymentOptions = [{ usdAmount: opts.price, amount: opts.price }];
262
+ if (opts.splits)
263
+ body.splits = JSON.parse(opts.splits);
258
264
  const data = await fetchJson(`${API_BASE}/api/endpoints/${id}`, {
259
265
  method: 'PUT',
260
266
  headers: await walletHeaders(privateKey),
@@ -518,11 +524,13 @@ program
518
524
  // ─── workflow-update ───
519
525
  program
520
526
  .command('workflow-update <id>')
521
- .description('Update a workflow by ID')
522
- .option('--name <name>', 'New name')
523
- .option('--description <desc>', 'New description')
527
+ .description('Update a workflow by ID (price/splits/graph changes auto-create a new version)')
528
+ .option('--name <name>', 'New name (7-day cooldown; 429 if on cooldown)')
529
+ .option('--description <desc>', 'New description (7-day cooldown)')
524
530
  .option('--status <status>', 'New status')
525
- .option('--price <usdc>', 'New total price')
531
+ .option('--price <usdc>', 'New total price — auto-bumps version')
532
+ .option('--splits <json>', 'Splits JSON array — auto-bumps version; redeploys splitter if structurally changed')
533
+ .option('--changelog <msg>', 'Label for the auto-created version snapshot')
526
534
  .option('-k, --key <privateKey>', 'Wallet private key')
527
535
  .action(async (id, opts) => {
528
536
  try {
@@ -536,6 +544,10 @@ program
536
544
  body.status = opts.status;
537
545
  if (opts.price)
538
546
  body.totalPrice = opts.price;
547
+ if (opts.splits)
548
+ body.splits = JSON.parse(opts.splits);
549
+ if (opts.changelog)
550
+ body.changelog = opts.changelog;
539
551
  const data = await fetchJson(`${API_BASE}/api/workflows/${id}`, {
540
552
  method: 'PUT',
541
553
  headers: await walletHeaders(privateKey),
package/dist/index.d.ts CHANGED
@@ -18,11 +18,50 @@ export interface PriceDiscovery {
18
18
  network: string;
19
19
  upstreamAccepts: any[];
20
20
  }
21
- export interface ApinowConfig {
21
+ /**
22
+ * Agent/server path — signs everything with a raw private key. Enables
23
+ * both x402 paid calls and signed-auth write calls.
24
+ */
25
+ export interface ApinowServerConfig {
22
26
  privateKey: `0x${string}`;
23
27
  baseUrl?: string;
24
28
  fetch?: typeof globalThis.fetch;
25
29
  }
30
+ /**
31
+ * Browser/wallet path — you provide a `signer` (any EIP-191 `personal_sign`
32
+ * function, e.g. from wagmi's `walletClient.signMessage`) and the connected
33
+ * `address`. Enables signed-auth writes. Paid x402 calls are NOT provided by
34
+ * this path — construct the x402 fetch yourself (see useX402Fetch pattern in
35
+ * the skill docs) and pass it to `paidFetch` if you need unified behaviour.
36
+ */
37
+ export interface ApinowBrowserConfig {
38
+ signer: (message: string) => Promise<`0x${string}` | string>;
39
+ address: `0x${string}`;
40
+ baseUrl?: string;
41
+ fetch?: typeof globalThis.fetch;
42
+ /** Optional externally-prepared x402 fetch for paid calls. */
43
+ paidFetch?: typeof globalThis.fetch;
44
+ }
45
+ export type ApinowConfig = ApinowServerConfig | ApinowBrowserConfig;
46
+ export interface SplitConfig {
47
+ address: string;
48
+ basisPoints: number;
49
+ label?: string;
50
+ tokenAddress?: string;
51
+ recipientAddress?: string;
52
+ }
53
+ /**
54
+ * Thrown by authed writes when the backend returns a non-2xx. Exposes status
55
+ * code and parsed JSON body so callers can branch on e.g. 429 cooldowns
56
+ * without re-parsing error text.
57
+ */
58
+ export declare class ApinowApiError extends Error {
59
+ readonly status: number;
60
+ readonly body: any;
61
+ readonly retryAfterMs?: number;
62
+ readonly retryAfterDays?: number;
63
+ constructor(status: number, body: any);
64
+ }
26
65
  export interface GenerateUIOptions {
27
66
  endpointName: string;
28
67
  namespace: string;
@@ -78,6 +117,13 @@ export declare function createClient(config: ApinowConfig): {
78
117
  * Get public endpoint info (free, no payment).
79
118
  */
80
119
  info(namespace: string, endpointName: string): Promise<any>;
120
+ /**
121
+ * Create an endpoint. If `splits` are provided they must sum to exactly
122
+ * `10000` basis points; the server deploys a 0xSplits V2 splitter after
123
+ * creation and stores `splitterAddress` on the endpoint. Non-fatal if
124
+ * the splitter deploy fails — endpoint will exist without one and can be
125
+ * retried via `updateEndpoint({ splits })`.
126
+ */
81
127
  createEndpoint(config: {
82
128
  namespace: string;
83
129
  endpointName: string;
@@ -96,9 +142,34 @@ export declare function createClient(config: ApinowConfig): {
96
142
  exampleQuery?: any;
97
143
  exampleOutput?: any;
98
144
  docsUrl?: string;
145
+ splits?: SplitConfig[];
99
146
  }): Promise<any>;
100
147
  getEndpoint(id: string): Promise<any>;
101
- updateEndpoint(id: string, updates: Record<string, any>): Promise<any>;
148
+ /**
149
+ * Update an endpoint. Changing `splits` (structurally) redeploys the
150
+ * splitter contract (V2 is immutable); the old address is archived on
151
+ * `previousSplitters[]` and the platform drains leftover USDC from it.
152
+ * `splits` must sum to `10000` basis points when non-empty.
153
+ */
154
+ updateEndpoint(id: string, updates: {
155
+ url?: string;
156
+ description?: string;
157
+ httpMethod?: "GET" | "POST";
158
+ paymentOptions?: Array<{
159
+ amount?: string;
160
+ usdAmount?: string;
161
+ tokenAddress?: string;
162
+ tokenSymbol?: string;
163
+ }>;
164
+ status?: string;
165
+ splits?: SplitConfig[];
166
+ querySchema?: any;
167
+ responseSchema?: any;
168
+ exampleQuery?: any;
169
+ exampleOutput?: any;
170
+ docsUrl?: string;
171
+ [key: string]: any;
172
+ }): Promise<any>;
102
173
  deleteEndpoint(id: string): Promise<any>;
103
174
  listEndpoints(opts?: {
104
175
  limit?: number;
@@ -133,15 +204,32 @@ export declare function createClient(config: ApinowConfig): {
133
204
  };
134
205
  prompt?: string;
135
206
  totalPrice?: string;
136
- splits?: Array<{
137
- address: string;
138
- basisPoints: number;
139
- label?: string;
140
- tokenAddress?: string;
141
- }>;
207
+ splits?: SplitConfig[];
142
208
  chain?: string;
143
209
  }): Promise<any>;
144
- updateWorkflow(workflowId: string, updates: Record<string, any>): Promise<any>;
210
+ /**
211
+ * Update a workflow. Changing `graph`, `totalPrice`, or `splits`
212
+ * auto-creates a new `WorkflowVersion` snapshot server-side; pass
213
+ * `changelog` to label it. `name`/`description` are subject to a 7-day
214
+ * cooldown — throws `ApinowApiError` with `status=429` and `retryAfterMs`
215
+ * when the cooldown is active.
216
+ */
217
+ updateWorkflow(workflowId: string, updates: {
218
+ name?: string;
219
+ description?: string;
220
+ status?: string;
221
+ totalPrice?: string;
222
+ splits?: SplitConfig[];
223
+ graph?: {
224
+ nodes: any[];
225
+ outputNode: string;
226
+ outputMapping?: any;
227
+ };
228
+ executionMode?: "balanced" | "optimistic" | "settle_first";
229
+ mermaidDiagram?: string;
230
+ changelog?: string;
231
+ [key: string]: any;
232
+ }): Promise<any>;
145
233
  deleteWorkflow(workflowId: string): Promise<any>;
146
234
  /**
147
235
  * List workflows you created (convenience for `listWorkflows({ creator: yourWallet })`).
@@ -161,8 +249,11 @@ export declare function createClient(config: ApinowConfig): {
161
249
  */
162
250
  getWorkflowVersion(workflowId: string, versionIdOrNumber: string | number): Promise<any>;
163
251
  /**
164
- * Create a new workflow version (creator only). Defaults to setting it as default.
165
- * Omit fields to inherit from current workflow.
252
+ * Create a new workflow version (creator only). Defaults to setting it as
253
+ * default. Omit fields to inherit from current workflow. If `splits`
254
+ * differs from the current config, the server redeploys the 0xSplits V2
255
+ * contract and stores `splitterAddress` / `splitterTxHash` on the version.
256
+ * Splits basis points must sum to `10000`.
166
257
  */
167
258
  createWorkflowVersion(workflowId: string, updates?: {
168
259
  graph?: {
@@ -171,12 +262,7 @@ export declare function createClient(config: ApinowConfig): {
171
262
  outputMapping?: any;
172
263
  };
173
264
  totalPrice?: string;
174
- splits?: Array<{
175
- address: string;
176
- basisPoints: number;
177
- label?: string;
178
- tokenAddress?: string;
179
- }>;
265
+ splits?: SplitConfig[];
180
266
  mermaidDiagram?: string;
181
267
  executionMode?: "balanced" | "optimistic" | "settle_first";
182
268
  changelog?: string;
package/dist/index.js CHANGED
@@ -2,6 +2,30 @@ import { x402Client, wrapFetchWithPayment } from '@x402/fetch';
2
2
  import { registerExactEvmScheme } from '@x402/evm/exact/client';
3
3
  import { privateKeyToAccount } from 'viem/accounts';
4
4
  const APINOW_BASE = 'https://apinow.fun';
5
+ function isServerConfig(c) {
6
+ return 'privateKey' in c && typeof c.privateKey === 'string';
7
+ }
8
+ /**
9
+ * Thrown by authed writes when the backend returns a non-2xx. Exposes status
10
+ * code and parsed JSON body so callers can branch on e.g. 429 cooldowns
11
+ * without re-parsing error text.
12
+ */
13
+ export class ApinowApiError extends Error {
14
+ constructor(status, body) {
15
+ const msg = (body && typeof body === 'object' && (body.error || body.message)) ||
16
+ (typeof body === 'string' ? body : JSON.stringify(body));
17
+ super(`APINow ${status}: ${msg}`);
18
+ this.name = 'ApinowApiError';
19
+ this.status = status;
20
+ this.body = body;
21
+ if (body && typeof body === 'object') {
22
+ if (typeof body.retryAfterMs === 'number')
23
+ this.retryAfterMs = body.retryAfterMs;
24
+ if (typeof body.retryAfterDays === 'number')
25
+ this.retryAfterDays = body.retryAfterDays;
26
+ }
27
+ }
28
+ }
5
29
  // ─── SDK ───
6
30
  // Prevent undici (Node 20+ / Vercel) from crashing when @x402/fetch retries
7
31
  // a POST with a cloned Request body stream and the server returns a 3xx redirect.
@@ -23,31 +47,50 @@ async function followRedirects(res) {
23
47
  return res;
24
48
  }
25
49
  export function createClient(config) {
26
- const { privateKey, baseUrl = APINOW_BASE, fetch: customFetch } = config;
27
- const account = privateKeyToAccount(privateKey);
28
- const client = new x402Client();
29
- registerExactEvmScheme(client, { signer: account });
30
- const safeFetch = makeSafeFetch(customFetch ?? fetch);
31
- const rawFetchWithPayment = wrapFetchWithPayment(safeFetch, client);
32
- const fetchWithPayment = (async (input, init) => {
33
- const res = await rawFetchWithPayment(input, init);
34
- return followRedirects(res);
35
- });
50
+ const server = isServerConfig(config);
51
+ const baseUrl = config.baseUrl ?? APINOW_BASE;
52
+ const customFetch = config.fetch;
53
+ // Resolve address + signer from whichever config shape the caller passed.
54
+ const account = server ? privateKeyToAccount(config.privateKey) : null;
55
+ const address = server
56
+ ? account.address
57
+ : config.address.toLowerCase();
58
+ const signMessage = server
59
+ ? (msg) => account.signMessage({ message: msg })
60
+ : (msg) => Promise.resolve(config.signer(msg)).then((s) => String(s));
61
+ // x402 paid fetch — only available when a private key is provided. Browsers
62
+ // should build their own x402 fetch (see skill.md useX402Fetch) and pass it
63
+ // as `paidFetch` to fall back through here.
64
+ const paidFetchExternal = !server ? config.paidFetch : undefined;
65
+ const fetchWithPayment = server
66
+ ? (() => {
67
+ const client = new x402Client();
68
+ registerExactEvmScheme(client, { signer: account });
69
+ const safeFetch = makeSafeFetch(customFetch ?? fetch);
70
+ const rawFetchWithPayment = wrapFetchWithPayment(safeFetch, client);
71
+ return (async (input, init) => {
72
+ const res = await rawFetchWithPayment(input, init);
73
+ return followRedirects(res);
74
+ });
75
+ })()
76
+ : (paidFetchExternal ??
77
+ (() => {
78
+ throw new Error('createClient: paid calls require a `privateKey` config or an explicit `paidFetch`. Pass an x402-wrapped fetch to make paid calls from the browser.');
79
+ }));
36
80
  /**
37
81
  * Produce an `Authorization: Bearer <msg>||<sig>||<addr>` header signed by
38
- * the wallet's private key. Backend verifies with ethers.recoverAddress and
39
- * rejects messages older than ~10 min.
40
- *
41
- * Exposed as a public helper so agents can sign custom write calls too.
82
+ * the wallet. Backend verifies with ethers.recoverAddress and rejects
83
+ * messages older than ~10 min. Works for both server (privateKey) and
84
+ * browser (walletClient.signMessage) configs.
42
85
  */
43
86
  async function signAuthHeader() {
44
87
  const issuedAt = new Date().toISOString();
45
88
  const nonce = Math.random().toString(36).slice(2) + Date.now().toString(36);
46
- const message = `APINow auth\naddress: ${account.address}\nissuedAt: ${issuedAt}\nnonce: ${nonce}`;
47
- const signature = await account.signMessage({ message });
89
+ const message = `APINow auth\naddress: ${address}\nissuedAt: ${issuedAt}\nnonce: ${nonce}`;
90
+ const signature = await signMessage(message);
48
91
  return {
49
- Authorization: `Bearer ${message}||${signature}||${account.address}`,
50
- 'x-wallet-address': account.address,
92
+ Authorization: `Bearer ${message}||${signature}||${address}`,
93
+ 'x-wallet-address': address,
51
94
  };
52
95
  }
53
96
  /**
@@ -70,12 +113,19 @@ export function createClient(config) {
70
113
  const res = await authedFetch(url, init);
71
114
  if (!res.ok) {
72
115
  const text = await res.text();
73
- throw new Error(`APINow ${res.status}: ${text}`);
116
+ let parsed = text;
117
+ try {
118
+ parsed = JSON.parse(text);
119
+ }
120
+ catch {
121
+ // leave as text
122
+ }
123
+ throw new ApinowApiError(res.status, parsed);
74
124
  }
75
125
  return res.json();
76
126
  }
77
127
  return {
78
- wallet: account.address,
128
+ wallet: address,
79
129
  /**
80
130
  * Produce a signed `Authorization` header for custom write calls.
81
131
  * Pairs with `x-wallet-address`. Backend accepts msg within ~10 min.
@@ -129,6 +179,13 @@ export function createClient(config) {
129
179
  return res.json();
130
180
  },
131
181
  // ─── Endpoint CRUD ───
182
+ /**
183
+ * Create an endpoint. If `splits` are provided they must sum to exactly
184
+ * `10000` basis points; the server deploys a 0xSplits V2 splitter after
185
+ * creation and stores `splitterAddress` on the endpoint. Non-fatal if
186
+ * the splitter deploy fails — endpoint will exist without one and can be
187
+ * retried via `updateEndpoint({ splits })`.
188
+ */
132
189
  async createEndpoint(config) {
133
190
  return authedJson(`${baseUrl}/api/endpoints`, {
134
191
  method: 'POST',
@@ -141,6 +198,12 @@ export function createClient(config) {
141
198
  throw new Error(`Failed to get endpoint: ${res.status}`);
142
199
  return res.json();
143
200
  },
201
+ /**
202
+ * Update an endpoint. Changing `splits` (structurally) redeploys the
203
+ * splitter contract (V2 is immutable); the old address is archived on
204
+ * `previousSplitters[]` and the platform drains leftover USDC from it.
205
+ * `splits` must sum to `10000` basis points when non-empty.
206
+ */
144
207
  async updateEndpoint(id, updates) {
145
208
  return authedJson(`${baseUrl}/api/endpoints/${id}`, {
146
209
  method: 'PUT',
@@ -197,6 +260,13 @@ export function createClient(config) {
197
260
  body: JSON.stringify(config),
198
261
  });
199
262
  },
263
+ /**
264
+ * Update a workflow. Changing `graph`, `totalPrice`, or `splits`
265
+ * auto-creates a new `WorkflowVersion` snapshot server-side; pass
266
+ * `changelog` to label it. `name`/`description` are subject to a 7-day
267
+ * cooldown — throws `ApinowApiError` with `status=429` and `retryAfterMs`
268
+ * when the cooldown is active.
269
+ */
200
270
  async updateWorkflow(workflowId, updates) {
201
271
  return authedJson(`${baseUrl}/api/workflows/${workflowId}`, {
202
272
  method: 'PUT',
@@ -210,7 +280,7 @@ export function createClient(config) {
210
280
  * List workflows you created (convenience for `listWorkflows({ creator: yourWallet })`).
211
281
  */
212
282
  async listMyWorkflows(opts = {}) {
213
- return this.listWorkflows({ ...opts, creator: account.address });
283
+ return this.listWorkflows({ ...opts, creator: address });
214
284
  },
215
285
  // ─── Workflow Versions ───
216
286
  /**
@@ -232,8 +302,11 @@ export function createClient(config) {
232
302
  return res.json();
233
303
  },
234
304
  /**
235
- * Create a new workflow version (creator only). Defaults to setting it as default.
236
- * Omit fields to inherit from current workflow.
305
+ * Create a new workflow version (creator only). Defaults to setting it as
306
+ * default. Omit fields to inherit from current workflow. If `splits`
307
+ * differs from the current config, the server redeploys the 0xSplits V2
308
+ * contract and stores `splitterAddress` / `splitterTxHash` on the version.
309
+ * Splits basis points must sum to `10000`.
237
310
  */
238
311
  async createWorkflowVersion(workflowId, updates = {}) {
239
312
  return authedJson(`${baseUrl}/api/workflows/${workflowId}/versions`, {
@@ -374,7 +447,7 @@ export function createClient(config) {
374
447
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
375
448
  method: 'POST',
376
449
  headers: { 'Content-Type': 'application/json' },
377
- body: JSON.stringify({ ...opts, walletAddress: account.address }),
450
+ body: JSON.stringify({ ...opts, walletAddress: address }),
378
451
  });
379
452
  if (!res.ok) {
380
453
  const text = await res.text();
@@ -409,7 +482,7 @@ export function createClient(config) {
409
482
  * Check free-tier UI generation eligibility for a wallet.
410
483
  */
411
484
  async checkFreeUI() {
412
- const params = new URLSearchParams({ checkFree: '1', wallet: account.address });
485
+ const params = new URLSearchParams({ checkFree: '1', wallet: address });
413
486
  const res = await fetch(`${baseUrl}/api/ai/generate-ui?${params}`);
414
487
  if (!res.ok)
415
488
  throw new Error(`Failed to check free tier: ${res.status}`);
@@ -419,7 +492,7 @@ export function createClient(config) {
419
492
  * Like, dislike, or comment on a generated UI.
420
493
  */
421
494
  async reactToUI(id, action, comment) {
422
- const body = { id, action, wallet: account.address };
495
+ const body = { id, action, wallet: address };
423
496
  if (comment)
424
497
  body.comment = comment;
425
498
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
@@ -440,7 +513,7 @@ export function createClient(config) {
440
513
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
441
514
  method: 'DELETE',
442
515
  headers: { 'Content-Type': 'application/json' },
443
- body: JSON.stringify({ id, wallet: account.address }),
516
+ body: JSON.stringify({ id, wallet: address }),
444
517
  });
445
518
  if (!res.ok) {
446
519
  const text = await res.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apinow-sdk",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Pay-per-call API SDK & CLI for APINow.fun — endpoints + workflows, wraps x402 so you don't have to",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",