apinow-sdk 0.24.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,9 +59,18 @@ Returns:
59
59
  | `discoverPrice(url, method?)` | Discover the x402 price of any URL (free) |
60
60
  | `search(query, limit?)` | Semantic search across all endpoints |
61
61
  | `info(ns, name)` | Get endpoint details (free) |
62
- | `listWorkflows(opts?)` | List workflows (free) |
63
- | `getWorkflow(id)` | Get workflow details (free) |
62
+ | `listWorkflows(opts?)` | List workflows (filter by `creator`, `status`) |
63
+ | `listMyWorkflows(opts?)` | List workflows created by your wallet |
64
+ | `getWorkflow(id)` | Get workflow details (incl. `currentVersion`, `creatorWallet`) |
65
+ | `createWorkflow(config)` | Create a workflow (seeds v1) |
66
+ | `updateWorkflow(id, updates)` | Update a workflow (auto-bumps version on graph/price/splits change) |
67
+ | `deleteWorkflow(id)` | Delete a workflow |
64
68
  | `runWorkflow(id, input)` | Run a workflow with automatic x402 payment |
69
+ | `listWorkflowVersions(id)` | List versions (free) |
70
+ | `getWorkflowVersion(id, vid)` | Get a specific version |
71
+ | `createWorkflowVersion(id, updates)` | Creator — new version, defaults to active |
72
+ | `setDefaultWorkflowVersion(id, vid)` | Promote/rollback a version |
73
+ | `deleteWorkflowVersion(id, vid)` | Delete a non-default version |
65
74
  | `generateUI(opts)` | Start AI UI generation for an endpoint (async) |
66
75
  | `generateUIAndWait(opts)` | Generate UI and poll until complete |
67
76
  | `getGeneratedUI(id)` | Get a generated UI by ID |
@@ -74,22 +83,44 @@ Returns:
74
83
 
75
84
  ### Workflows
76
85
 
77
- Workflows chain multiple x402 endpoints into a single paid DAG pipeline with automatic payment splitting.
86
+ Workflows chain multiple x402 endpoints into a single paid DAG pipeline with automatic payment splitting. Each workflow is owned by a `creatorWallet` and tracks an immutable version history.
78
87
 
79
88
  ```typescript
80
- // list workflows
81
- const { workflows } = await apinow.listWorkflows({ status: 'active', limit: 10 });
89
+ // list workflows (optionally filter by creator)
90
+ const { workflows } = await apinow.listWorkflows({ creator: '0x...', status: 'active' });
82
91
 
83
- // get workflow details (nodes, splits, pricing)
84
- const workflow = await apinow.getWorkflow('90931d9c8fb94df9');
85
- console.log(workflow.name, workflow.totalPrice, workflow.graph.nodes);
92
+ // your own workflows
93
+ const mine = await apinow.listMyWorkflows();
94
+
95
+ // get workflow details (nodes, splits, pricing, currentVersion, creatorWallet)
96
+ const workflow = await apinow.getWorkflow('f5d40784593aa972');
86
97
 
87
98
  // run a workflow — x402 payment covers all nodes + creator split
88
- const result = await apinow.runWorkflow('90931d9c8fb94df9', {
99
+ const result = await apinow.runWorkflow('f5d40784593aa972', {
89
100
  query: 'birthday gift ideas for a friend who loves cooking',
90
101
  });
91
102
  ```
92
103
 
104
+ #### Versions & metadata cooldown
105
+
106
+ - `PUT /api/workflows/{id}` with changes to `graph`, `totalPrice`, or `splits` auto-creates a new version.
107
+ - `name` and `description` can only change **once every 7 days** per workflow (server returns `429` with `retryAfterMs`).
108
+ - To iterate freely, create a new version — no cooldown.
109
+
110
+ ```typescript
111
+ // snapshot history
112
+ const { versions } = await apinow.listWorkflowVersions('f5d40784593aa972');
113
+
114
+ // bump price without renaming
115
+ await apinow.createWorkflowVersion('f5d40784593aa972', {
116
+ totalPrice: '0.12',
117
+ changelog: 'Raised price after usage spike',
118
+ });
119
+
120
+ // rollback
121
+ await apinow.setDefaultWorkflowVersion('f5d40784593aa972', 1);
122
+ ```
123
+
93
124
  ### AI UI Generation
94
125
 
95
126
  Generate interactive Arrow JS sandbox UIs for any endpoint — ideal for AI agents that need a visual interface.
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.24.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;
@@ -48,8 +48,14 @@ function getWallet(opts) {
48
48
  const account = privateKeyToAccount(privateKey);
49
49
  return { privateKey, address: account.address };
50
50
  }
51
- function walletHeaders(address) {
52
- return { 'Content-Type': 'application/json', 'x-wallet-address': address };
51
+ /**
52
+ * Signed headers for mutating calls. The backend verifies the signature
53
+ * recovers to the claimed wallet address before accepting the request.
54
+ */
55
+ async function walletHeaders(privateKey) {
56
+ const apinow = createClient({ privateKey });
57
+ const auth = await apinow.signAuthHeader();
58
+ return { 'Content-Type': 'application/json', ...auth };
53
59
  }
54
60
  // ─── search ───
55
61
  program
@@ -207,7 +213,7 @@ program
207
213
  .option('-k, --key <privateKey>', 'Wallet private key')
208
214
  .action(async (opts) => {
209
215
  try {
210
- const { address } = getWallet(opts);
216
+ const { privateKey, address } = getWallet(opts);
211
217
  const body = {
212
218
  namespace: opts.namespace,
213
219
  endpointName: opts.name,
@@ -218,7 +224,7 @@ program
218
224
  };
219
225
  const data = await fetchJson(`${API_BASE}/api/endpoints`, {
220
226
  method: 'POST',
221
- headers: walletHeaders(address),
227
+ headers: await walletHeaders(privateKey),
222
228
  body: JSON.stringify(body),
223
229
  });
224
230
  console.log(JSON.stringify(data, null, 2));
@@ -239,7 +245,7 @@ program
239
245
  .option('-k, --key <privateKey>', 'Wallet private key')
240
246
  .action(async (id, opts) => {
241
247
  try {
242
- const { address } = getWallet(opts);
248
+ const { privateKey, address } = getWallet(opts);
243
249
  const body = {};
244
250
  if (opts.description)
245
251
  body.description = opts.description;
@@ -251,7 +257,7 @@ program
251
257
  body.paymentOptions = [{ usdAmount: opts.price, amount: opts.price }];
252
258
  const data = await fetchJson(`${API_BASE}/api/endpoints/${id}`, {
253
259
  method: 'PUT',
254
- headers: walletHeaders(address),
260
+ headers: await walletHeaders(privateKey),
255
261
  body: JSON.stringify(body),
256
262
  });
257
263
  console.log(JSON.stringify(data, null, 2));
@@ -268,10 +274,10 @@ program
268
274
  .option('-k, --key <privateKey>', 'Wallet private key')
269
275
  .action(async (id, opts) => {
270
276
  try {
271
- const { address } = getWallet(opts);
277
+ const { privateKey, address } = getWallet(opts);
272
278
  const data = await fetchJson(`${API_BASE}/api/endpoints/${id}`, {
273
279
  method: 'DELETE',
274
- headers: walletHeaders(address),
280
+ headers: await walletHeaders(privateKey),
275
281
  });
276
282
  console.log(JSON.stringify(data, null, 2));
277
283
  }
@@ -425,6 +431,7 @@ program
425
431
  console.log(` ${data.description || '(no description)'}\n`);
426
432
  console.log(` ID: ${data.workflowId}`);
427
433
  console.log(` Status: ${data.status}`);
434
+ console.log(` Version: v${data.currentVersion ?? 1}${data.currentVersionId ? ` (${data.currentVersionId.slice(0, 8)}…)` : ''}`);
428
435
  console.log(` Cost: $${data.totalPrice} USDC`);
429
436
  console.log(` Chain: ${data.chain || 'base'}`);
430
437
  console.log(` Creator: ${data.creatorWallet}`);
@@ -486,7 +493,7 @@ program
486
493
  .option('-k, --key <privateKey>', 'Wallet private key')
487
494
  .action(async (opts) => {
488
495
  try {
489
- const { address } = getWallet(opts);
496
+ const { privateKey, address } = getWallet(opts);
490
497
  const body = { name: opts.name, totalPrice: opts.price };
491
498
  if (opts.description)
492
499
  body.description = opts.description;
@@ -498,7 +505,7 @@ program
498
505
  body.splits = JSON.parse(opts.splits);
499
506
  const data = await fetchJson(`${API_BASE}/api/workflows`, {
500
507
  method: 'POST',
501
- headers: walletHeaders(address),
508
+ headers: await walletHeaders(privateKey),
502
509
  body: JSON.stringify(body),
503
510
  });
504
511
  console.log(JSON.stringify(data, null, 2));
@@ -519,7 +526,7 @@ program
519
526
  .option('-k, --key <privateKey>', 'Wallet private key')
520
527
  .action(async (id, opts) => {
521
528
  try {
522
- const { address } = getWallet(opts);
529
+ const { privateKey, address } = getWallet(opts);
523
530
  const body = {};
524
531
  if (opts.name)
525
532
  body.name = opts.name;
@@ -531,7 +538,7 @@ program
531
538
  body.totalPrice = opts.price;
532
539
  const data = await fetchJson(`${API_BASE}/api/workflows/${id}`, {
533
540
  method: 'PUT',
534
- headers: walletHeaders(address),
541
+ headers: await walletHeaders(privateKey),
535
542
  body: JSON.stringify(body),
536
543
  });
537
544
  console.log(JSON.stringify(data, null, 2));
@@ -548,10 +555,154 @@ program
548
555
  .option('-k, --key <privateKey>', 'Wallet private key')
549
556
  .action(async (id, opts) => {
550
557
  try {
551
- const { address } = getWallet(opts);
558
+ const { privateKey, address } = getWallet(opts);
552
559
  const data = await fetchJson(`${API_BASE}/api/workflows/${id}`, {
553
560
  method: 'DELETE',
554
- headers: walletHeaders(address),
561
+ headers: await walletHeaders(privateKey),
562
+ });
563
+ console.log(JSON.stringify(data, null, 2));
564
+ }
565
+ catch (err) {
566
+ console.error(`Error: ${err.message}`);
567
+ process.exit(1);
568
+ }
569
+ });
570
+ // ─── my-workflows (creator endpoint view) ───
571
+ program
572
+ .command('my-workflows')
573
+ .description('List workflows you created (uses your wallet)')
574
+ .option('-l, --limit <n>', 'Max results', '50')
575
+ .option('-s, --status <status>', 'Filter: active | draft | paused | all', 'all')
576
+ .option('-k, --key <privateKey>', 'Wallet private key')
577
+ .action(async (opts) => {
578
+ try {
579
+ const { privateKey, address } = getWallet(opts);
580
+ const params = new URLSearchParams({
581
+ limit: opts.limit,
582
+ status: opts.status,
583
+ creator: address,
584
+ });
585
+ const data = await fetchJson(`${API_BASE}/api/workflows?${params}`);
586
+ const workflows = data.workflows || [];
587
+ if (!workflows.length) {
588
+ console.log('No workflows found for this wallet.');
589
+ return;
590
+ }
591
+ const rows = [['ID', 'NAME', 'STATUS', 'VER', 'NODES', 'COST']];
592
+ for (const w of workflows) {
593
+ rows.push([
594
+ w.workflowId,
595
+ truncate(w.name || '', 24),
596
+ w.status,
597
+ `v${w.currentVersion ?? 1}`,
598
+ String(w.graph?.nodes?.length || 0),
599
+ `$${w.totalPrice}`,
600
+ ]);
601
+ }
602
+ printTable(rows);
603
+ }
604
+ catch (err) {
605
+ console.error(`Error: ${err.message}`);
606
+ process.exit(1);
607
+ }
608
+ });
609
+ // ─── workflow-versions ───
610
+ program
611
+ .command('workflow-versions <id>')
612
+ .description('List all versions of a workflow')
613
+ .action(async (id) => {
614
+ try {
615
+ const data = await fetchJson(`${API_BASE}/api/workflows/${id}/versions`);
616
+ const versions = data.versions || [];
617
+ if (!versions.length) {
618
+ console.log('No versions found.');
619
+ return;
620
+ }
621
+ const rows = [['VER', 'DEFAULT', 'VERSION_ID', 'PRICE', 'CREATED', 'CHANGELOG']];
622
+ for (const v of versions) {
623
+ rows.push([
624
+ `v${v.version}`,
625
+ v.isDefault ? '★' : '',
626
+ v.versionId,
627
+ `$${v.totalPrice}`,
628
+ new Date(v.createdAt).toLocaleDateString(),
629
+ truncate(v.changelog || '', 40),
630
+ ]);
631
+ }
632
+ printTable(rows);
633
+ }
634
+ catch (err) {
635
+ console.error(`Error: ${err.message}`);
636
+ process.exit(1);
637
+ }
638
+ });
639
+ // ─── workflow-version-create ───
640
+ program
641
+ .command('workflow-version-create <id>')
642
+ .description('Create a new version of a workflow (graph/price/splits)')
643
+ .option('--graph <json>', 'Graph JSON (inherits current if omitted)')
644
+ .option('--price <usdc>', 'Total price')
645
+ .option('--splits <json>', 'Splits JSON array')
646
+ .option('--changelog <msg>', 'Changelog message')
647
+ .option('--no-default', 'Create as non-default (keeps current version active)')
648
+ .option('-k, --key <privateKey>', 'Wallet private key')
649
+ .action(async (id, opts) => {
650
+ try {
651
+ const { privateKey, address } = getWallet(opts);
652
+ const body = {};
653
+ if (opts.graph)
654
+ body.graph = JSON.parse(opts.graph);
655
+ if (opts.price)
656
+ body.totalPrice = opts.price;
657
+ if (opts.splits)
658
+ body.splits = JSON.parse(opts.splits);
659
+ if (opts.changelog)
660
+ body.changelog = opts.changelog;
661
+ if (opts.default === false)
662
+ body.setDefault = false;
663
+ const data = await fetchJson(`${API_BASE}/api/workflows/${id}/versions`, {
664
+ method: 'POST',
665
+ headers: await walletHeaders(privateKey),
666
+ body: JSON.stringify(body),
667
+ });
668
+ console.log(JSON.stringify(data, null, 2));
669
+ }
670
+ catch (err) {
671
+ console.error(`Error: ${err.message}`);
672
+ process.exit(1);
673
+ }
674
+ });
675
+ // ─── workflow-version-set-default ───
676
+ program
677
+ .command('workflow-version-set-default <id> <versionIdOrNumber>')
678
+ .description('Set a version as the active/default for a workflow (rollback/promote)')
679
+ .option('-k, --key <privateKey>', 'Wallet private key')
680
+ .action(async (id, vid, opts) => {
681
+ try {
682
+ const { privateKey, address } = getWallet(opts);
683
+ const data = await fetchJson(`${API_BASE}/api/workflows/${id}/versions/${vid}`, {
684
+ method: 'PUT',
685
+ headers: await walletHeaders(privateKey),
686
+ body: JSON.stringify({ setDefault: true }),
687
+ });
688
+ console.log(JSON.stringify(data, null, 2));
689
+ }
690
+ catch (err) {
691
+ console.error(`Error: ${err.message}`);
692
+ process.exit(1);
693
+ }
694
+ });
695
+ // ─── workflow-version-delete ───
696
+ program
697
+ .command('workflow-version-delete <id> <versionIdOrNumber>')
698
+ .description('Delete a non-default workflow version')
699
+ .option('-k, --key <privateKey>', 'Wallet private key')
700
+ .action(async (id, vid, opts) => {
701
+ try {
702
+ const { privateKey, address } = getWallet(opts);
703
+ const data = await fetchJson(`${API_BASE}/api/workflows/${id}/versions/${vid}`, {
704
+ method: 'DELETE',
705
+ headers: await walletHeaders(privateKey),
555
706
  });
556
707
  console.log(JSON.stringify(data, null, 2));
557
708
  }
@@ -573,7 +724,7 @@ program
573
724
  if (!endpoint.includes('/'))
574
725
  throw new Error('Format: namespace/endpoint-name');
575
726
  const [ns, ep] = endpoint.split('/');
576
- const { address } = getWallet(opts);
727
+ const { privateKey, address } = getWallet(opts);
577
728
  const details = await fetchJson(`${API_BASE}/api/endpoints/${ns}/${ep}/details`);
578
729
  const body = {
579
730
  endpointName: ep,
@@ -693,7 +844,7 @@ program
693
844
  .option('-k, --key <privateKey>', 'Wallet private key')
694
845
  .action(async (id, opts) => {
695
846
  try {
696
- const { address } = getWallet(opts);
847
+ const { privateKey, address } = getWallet(opts);
697
848
  const data = await fetchJson(`${API_BASE}/api/ai/generate-ui`, {
698
849
  method: 'DELETE',
699
850
  headers: { 'Content-Type': 'application/json' },
@@ -713,7 +864,7 @@ program
713
864
  .option('-k, --key <privateKey>', 'Wallet private key')
714
865
  .action(async (id, opts) => {
715
866
  try {
716
- const { address } = getWallet(opts);
867
+ const { privateKey, address } = getWallet(opts);
717
868
  const data = await fetchJson(`${API_BASE}/api/ai/generate-ui`, {
718
869
  method: 'PATCH',
719
870
  headers: { 'Content-Type': 'application/json' },
@@ -732,7 +883,7 @@ program
732
883
  .option('-k, --key <privateKey>', 'Wallet private key')
733
884
  .action(async (id, opts) => {
734
885
  try {
735
- const { address } = getWallet(opts);
886
+ const { privateKey, address } = getWallet(opts);
736
887
  const data = await fetchJson(`${API_BASE}/api/ai/generate-ui`, {
737
888
  method: 'PATCH',
738
889
  headers: { 'Content-Type': 'application/json' },
@@ -753,7 +904,7 @@ program
753
904
  .option('-k, --key <privateKey>', 'Wallet private key')
754
905
  .action(async (id, opts) => {
755
906
  try {
756
- const { address } = getWallet(opts);
907
+ const { privateKey, address } = getWallet(opts);
757
908
  const data = await fetchJson(`${API_BASE}/api/ai/generate-ui`, {
758
909
  method: 'PATCH',
759
910
  headers: { 'Content-Type': 'application/json' },
@@ -773,7 +924,7 @@ program
773
924
  .option('-k, --key <privateKey>', 'Wallet private key')
774
925
  .action(async (opts) => {
775
926
  try {
776
- const { address } = getWallet(opts);
927
+ const { privateKey, address } = getWallet(opts);
777
928
  const params = new URLSearchParams({ checkFree: '1', wallet: address });
778
929
  const data = await fetchJson(`${API_BASE}/api/ai/generate-ui?${params}`);
779
930
  console.log(`\n Wallet: ${address}`);
@@ -792,9 +943,9 @@ program
792
943
  .option('-k, --key <privateKey>', 'Wallet private key')
793
944
  .action(async (opts) => {
794
945
  try {
795
- const { address } = getWallet(opts);
946
+ const { privateKey, address } = getWallet(opts);
796
947
  const data = await fetchJson(`${API_BASE}/api/user-factory/check-balance`, {
797
- headers: walletHeaders(address),
948
+ headers: await walletHeaders(privateKey),
798
949
  });
799
950
  console.log(`\n Wallet: ${address}`);
800
951
  console.log(` Balance: ${data.balance} $APINOW`);
@@ -813,9 +964,9 @@ program
813
964
  .option('-k, --key <privateKey>', 'Wallet private key')
814
965
  .action(async (opts) => {
815
966
  try {
816
- const { address } = getWallet(opts);
967
+ const { privateKey, address } = getWallet(opts);
817
968
  const data = await fetchJson(`${API_BASE}/api/user-factory`, {
818
- headers: walletHeaders(address),
969
+ headers: await walletHeaders(privateKey),
819
970
  });
820
971
  const endpoints = data.endpoints || [];
821
972
  if (!endpoints.length) {
@@ -845,11 +996,11 @@ program
845
996
  .option('-k, --key <privateKey>', 'Wallet private key')
846
997
  .action(async (idea, opts) => {
847
998
  try {
848
- const { address } = getWallet(opts);
999
+ const { privateKey, address } = getWallet(opts);
849
1000
  console.error('Generating endpoint config…');
850
1001
  const data = await fetchJson(`${API_BASE}/api/user-factory/generate`, {
851
1002
  method: 'POST',
852
- headers: walletHeaders(address),
1003
+ headers: await walletHeaders(privateKey),
853
1004
  body: JSON.stringify({ idea }),
854
1005
  });
855
1006
  console.log(JSON.stringify(data, null, 2));
@@ -876,7 +1027,7 @@ program
876
1027
  .option('-k, --key <privateKey>', 'Wallet private key')
877
1028
  .action(async (opts) => {
878
1029
  try {
879
- const { address } = getWallet(opts);
1030
+ const { privateKey, address } = getWallet(opts);
880
1031
  let body;
881
1032
  if (opts.fromJson) {
882
1033
  const { readFileSync } = await import('fs');
@@ -933,7 +1084,7 @@ program
933
1084
  console.error(`Creating endpoint "${body.name}"…`);
934
1085
  const data = await fetchJson(`${API_BASE}/api/user-factory`, {
935
1086
  method: 'POST',
936
- headers: walletHeaders(address),
1087
+ headers: await walletHeaders(privateKey),
937
1088
  body: JSON.stringify(body),
938
1089
  });
939
1090
  const ep = data.endpoint;
@@ -961,7 +1112,7 @@ program
961
1112
  .option('-k, --key <privateKey>', 'Wallet private key')
962
1113
  .action(async (endpointId, opts) => {
963
1114
  try {
964
- const { address } = getWallet(opts);
1115
+ const { privateKey, address } = getWallet(opts);
965
1116
  const body = { endpointId, markupPercent: Number(opts.markup) };
966
1117
  if (opts.name)
967
1118
  body.workflowName = opts.name;
@@ -977,7 +1128,7 @@ program
977
1128
  console.error(`Creating markup workflow (${opts.markup}%${tbLabel})…`);
978
1129
  const data = await fetchJson(`${API_BASE}/api/user-factory/markup`, {
979
1130
  method: 'POST',
980
- headers: walletHeaders(address),
1131
+ headers: await walletHeaders(privateKey),
981
1132
  body: JSON.stringify(body),
982
1133
  });
983
1134
  const w = data.workflow;
@@ -1006,7 +1157,7 @@ program
1006
1157
  try {
1007
1158
  if (!endpoint.includes('/'))
1008
1159
  throw new Error('Format: namespace/endpoint-name');
1009
- const { address } = getWallet(opts);
1160
+ const { privateKey, address } = getWallet(opts);
1010
1161
  const [namespace, endpointName] = endpoint.split('/');
1011
1162
  const body = { namespace, endpointName };
1012
1163
  if (opts.data)
@@ -1016,7 +1167,7 @@ program
1016
1167
  console.error(`Testing ${endpoint}…`);
1017
1168
  const data = await fetchJson(`${API_BASE}/api/user-factory/test-call`, {
1018
1169
  method: 'POST',
1019
- headers: walletHeaders(address),
1170
+ headers: await walletHeaders(privateKey),
1020
1171
  body: JSON.stringify(body),
1021
1172
  });
1022
1173
  console.log(JSON.stringify(data.output, null, 2));
@@ -1044,12 +1195,12 @@ program
1044
1195
  .option('-k, --key <privateKey>', 'Wallet private key')
1045
1196
  .action(async (idea, opts) => {
1046
1197
  try {
1047
- const { address } = getWallet(opts);
1198
+ const { privateKey, address } = getWallet(opts);
1048
1199
  // Step 1: Generate
1049
1200
  console.error('Step 1/4: Generating endpoint config from idea…');
1050
1201
  const draft = await fetchJson(`${API_BASE}/api/user-factory/generate`, {
1051
1202
  method: 'POST',
1052
- headers: walletHeaders(address),
1203
+ headers: await walletHeaders(privateKey),
1053
1204
  body: JSON.stringify({ idea }),
1054
1205
  });
1055
1206
  console.error(` → ${draft.name}: ${draft.description}`);
@@ -1075,7 +1226,7 @@ program
1075
1226
  createBody.recipientWallet = opts.recipient;
1076
1227
  const createData = await fetchJson(`${API_BASE}/api/user-factory`, {
1077
1228
  method: 'POST',
1078
- headers: walletHeaders(address),
1229
+ headers: await walletHeaders(privateKey),
1079
1230
  body: JSON.stringify(createBody),
1080
1231
  });
1081
1232
  const ep = createData.endpoint;
@@ -1085,7 +1236,7 @@ program
1085
1236
  const testInput = draft.exampleInput || { prompt: 'test' };
1086
1237
  const testData = await fetchJson(`${API_BASE}/api/user-factory/test-call`, {
1087
1238
  method: 'POST',
1088
- headers: walletHeaders(address),
1239
+ headers: await walletHeaders(privateKey),
1089
1240
  body: JSON.stringify({
1090
1241
  namespace: ep.namespace,
1091
1242
  endpointName: ep.endpointName,
@@ -1112,7 +1263,7 @@ program
1112
1263
  markupBody.tokenBuyCA = opts.tokenBuyCa;
1113
1264
  const markupData = await fetchJson(`${API_BASE}/api/user-factory/markup`, {
1114
1265
  method: 'POST',
1115
- headers: walletHeaders(address),
1266
+ headers: await walletHeaders(privateKey),
1116
1267
  body: JSON.stringify(markupBody),
1117
1268
  });
1118
1269
  workflow = markupData.workflow;
package/dist/index.d.ts CHANGED
@@ -18,11 +18,31 @@ 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;
26
46
  export interface GenerateUIOptions {
27
47
  endpointName: string;
28
48
  namespace: string;
@@ -55,6 +75,11 @@ export interface GeneratedUI {
55
75
  }
56
76
  export declare function createClient(config: ApinowConfig): {
57
77
  wallet: `0x${string}`;
78
+ /**
79
+ * Produce a signed `Authorization` header for custom write calls.
80
+ * Pairs with `x-wallet-address`. Backend accepts msg within ~10 min.
81
+ */
82
+ signAuthHeader: () => Promise<Record<string, string>>;
58
83
  /**
59
84
  * Call any APINow endpoint. Handles x402 payment automatically.
60
85
  *
@@ -138,6 +163,52 @@ export declare function createClient(config: ApinowConfig): {
138
163
  }): Promise<any>;
139
164
  updateWorkflow(workflowId: string, updates: Record<string, any>): Promise<any>;
140
165
  deleteWorkflow(workflowId: string): Promise<any>;
166
+ /**
167
+ * List workflows you created (convenience for `listWorkflows({ creator: yourWallet })`).
168
+ */
169
+ listMyWorkflows(opts?: {
170
+ status?: string;
171
+ limit?: number;
172
+ }): Promise<any>;
173
+ /**
174
+ * List all versions of a workflow (public, free).
175
+ */
176
+ listWorkflowVersions(workflowId: string): Promise<{
177
+ versions: any[];
178
+ }>;
179
+ /**
180
+ * Get a specific workflow version by versionId or numeric version.
181
+ */
182
+ getWorkflowVersion(workflowId: string, versionIdOrNumber: string | number): Promise<any>;
183
+ /**
184
+ * Create a new workflow version (creator only). Defaults to setting it as default.
185
+ * Omit fields to inherit from current workflow.
186
+ */
187
+ createWorkflowVersion(workflowId: string, updates?: {
188
+ graph?: {
189
+ nodes: any[];
190
+ outputNode: string;
191
+ outputMapping?: any;
192
+ };
193
+ totalPrice?: string;
194
+ splits?: Array<{
195
+ address: string;
196
+ basisPoints: number;
197
+ label?: string;
198
+ tokenAddress?: string;
199
+ }>;
200
+ mermaidDiagram?: string;
201
+ executionMode?: "balanced" | "optimistic" | "settle_first";
202
+ changelog?: string;
203
+ setDefault?: boolean;
204
+ forkedFrom?: string;
205
+ }): Promise<any>;
206
+ /**
207
+ * Set a version as the default (active) for a workflow. Also rolls the
208
+ * workflow's graph/price/splits back to that version's snapshot.
209
+ */
210
+ setDefaultWorkflowVersion(workflowId: string, versionIdOrNumber: string | number): Promise<any>;
211
+ deleteWorkflowVersion(workflowId: string, versionIdOrNumber: string | number): Promise<any>;
141
212
  /**
142
213
  * Run a workflow. Handles x402 payment automatically.
143
214
  *
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@ 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
+ }
5
8
  // ─── SDK ───
6
9
  // Prevent undici (Node 20+ / Vercel) from crashing when @x402/fetch retries
7
10
  // a POST with a cloned Request body stream and the server returns a 3xx redirect.
@@ -23,18 +26,83 @@ async function followRedirects(res) {
23
26
  return res;
24
27
  }
25
28
  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
- });
29
+ const server = isServerConfig(config);
30
+ const baseUrl = config.baseUrl ?? APINOW_BASE;
31
+ const customFetch = config.fetch;
32
+ // Resolve address + signer from whichever config shape the caller passed.
33
+ const account = server ? privateKeyToAccount(config.privateKey) : null;
34
+ const address = server
35
+ ? account.address
36
+ : config.address.toLowerCase();
37
+ const signMessage = server
38
+ ? (msg) => account.signMessage({ message: msg })
39
+ : (msg) => Promise.resolve(config.signer(msg)).then((s) => String(s));
40
+ // x402 paid fetch — only available when a private key is provided. Browsers
41
+ // should build their own x402 fetch (see skill.md useX402Fetch) and pass it
42
+ // as `paidFetch` to fall back through here.
43
+ const paidFetchExternal = !server ? config.paidFetch : undefined;
44
+ const fetchWithPayment = server
45
+ ? (() => {
46
+ const client = new x402Client();
47
+ registerExactEvmScheme(client, { signer: account });
48
+ const safeFetch = makeSafeFetch(customFetch ?? fetch);
49
+ const rawFetchWithPayment = wrapFetchWithPayment(safeFetch, client);
50
+ return (async (input, init) => {
51
+ const res = await rawFetchWithPayment(input, init);
52
+ return followRedirects(res);
53
+ });
54
+ })()
55
+ : (paidFetchExternal ??
56
+ (() => {
57
+ 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.');
58
+ }));
59
+ /**
60
+ * Produce an `Authorization: Bearer <msg>||<sig>||<addr>` header signed by
61
+ * the wallet. Backend verifies with ethers.recoverAddress and rejects
62
+ * messages older than ~10 min. Works for both server (privateKey) and
63
+ * browser (walletClient.signMessage) configs.
64
+ */
65
+ async function signAuthHeader() {
66
+ const issuedAt = new Date().toISOString();
67
+ const nonce = Math.random().toString(36).slice(2) + Date.now().toString(36);
68
+ const message = `APINow auth\naddress: ${address}\nissuedAt: ${issuedAt}\nnonce: ${nonce}`;
69
+ const signature = await signMessage(message);
70
+ return {
71
+ Authorization: `Bearer ${message}||${signature}||${address}`,
72
+ 'x-wallet-address': address,
73
+ };
74
+ }
75
+ /**
76
+ * Fetch wrapper that signs write requests with the wallet private key.
77
+ * Use for any non-paid mutating API (endpoint/workflow/version CRUD).
78
+ */
79
+ async function authedFetch(url, init = {}) {
80
+ const authHeaders = await signAuthHeader();
81
+ const res = await fetch(url, {
82
+ ...init,
83
+ headers: {
84
+ ...(init.body ? { 'Content-Type': 'application/json' } : {}),
85
+ ...authHeaders,
86
+ ...init.headers,
87
+ },
88
+ });
89
+ return res;
90
+ }
91
+ async function authedJson(url, init = {}) {
92
+ const res = await authedFetch(url, init);
93
+ if (!res.ok) {
94
+ const text = await res.text();
95
+ throw new Error(`APINow ${res.status}: ${text}`);
96
+ }
97
+ return res.json();
98
+ }
36
99
  return {
37
- wallet: account.address,
100
+ wallet: address,
101
+ /**
102
+ * Produce a signed `Authorization` header for custom write calls.
103
+ * Pairs with `x-wallet-address`. Backend accepts msg within ~10 min.
104
+ */
105
+ signAuthHeader,
38
106
  /**
39
107
  * Call any APINow endpoint. Handles x402 payment automatically.
40
108
  *
@@ -84,16 +152,10 @@ export function createClient(config) {
84
152
  },
85
153
  // ─── Endpoint CRUD ───
86
154
  async createEndpoint(config) {
87
- const res = await fetch(`${baseUrl}/api/endpoints`, {
155
+ return authedJson(`${baseUrl}/api/endpoints`, {
88
156
  method: 'POST',
89
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
90
157
  body: JSON.stringify(config),
91
158
  });
92
- if (!res.ok) {
93
- const text = await res.text();
94
- throw new Error(`Failed to create endpoint: ${res.status} ${text}`);
95
- }
96
- return res.json();
97
159
  },
98
160
  async getEndpoint(id) {
99
161
  const res = await fetch(`${baseUrl}/api/endpoints/${id}`);
@@ -102,27 +164,13 @@ export function createClient(config) {
102
164
  return res.json();
103
165
  },
104
166
  async updateEndpoint(id, updates) {
105
- const res = await fetch(`${baseUrl}/api/endpoints/${id}`, {
167
+ return authedJson(`${baseUrl}/api/endpoints/${id}`, {
106
168
  method: 'PUT',
107
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
108
169
  body: JSON.stringify(updates),
109
170
  });
110
- if (!res.ok) {
111
- const text = await res.text();
112
- throw new Error(`Failed to update endpoint: ${res.status} ${text}`);
113
- }
114
- return res.json();
115
171
  },
116
172
  async deleteEndpoint(id) {
117
- const res = await fetch(`${baseUrl}/api/endpoints/${id}`, {
118
- method: 'DELETE',
119
- headers: { 'x-wallet-address': account.address },
120
- });
121
- if (!res.ok) {
122
- const text = await res.text();
123
- throw new Error(`Failed to delete endpoint: ${res.status} ${text}`);
124
- }
125
- return res.json();
173
+ return authedJson(`${baseUrl}/api/endpoints/${id}`, { method: 'DELETE' });
126
174
  },
127
175
  async listEndpoints(opts = {}) {
128
176
  const params = new URLSearchParams();
@@ -166,40 +214,68 @@ export function createClient(config) {
166
214
  return res.json();
167
215
  },
168
216
  async createWorkflow(config) {
169
- const res = await fetch(`${baseUrl}/api/workflows`, {
217
+ return authedJson(`${baseUrl}/api/workflows`, {
170
218
  method: 'POST',
171
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
172
219
  body: JSON.stringify(config),
173
220
  });
174
- if (!res.ok) {
175
- const text = await res.text();
176
- throw new Error(`Failed to create workflow: ${res.status} ${text}`);
177
- }
178
- return res.json();
179
221
  },
180
222
  async updateWorkflow(workflowId, updates) {
181
- const res = await fetch(`${baseUrl}/api/workflows/${workflowId}`, {
223
+ return authedJson(`${baseUrl}/api/workflows/${workflowId}`, {
182
224
  method: 'PUT',
183
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
184
225
  body: JSON.stringify(updates),
185
226
  });
186
- if (!res.ok) {
187
- const text = await res.text();
188
- throw new Error(`Failed to update workflow: ${res.status} ${text}`);
189
- }
190
- return res.json();
191
227
  },
192
228
  async deleteWorkflow(workflowId) {
193
- const res = await fetch(`${baseUrl}/api/workflows/${workflowId}`, {
194
- method: 'DELETE',
195
- headers: { 'x-wallet-address': account.address },
196
- });
197
- if (!res.ok) {
198
- const text = await res.text();
199
- throw new Error(`Failed to delete workflow: ${res.status} ${text}`);
200
- }
229
+ return authedJson(`${baseUrl}/api/workflows/${workflowId}`, { method: 'DELETE' });
230
+ },
231
+ /**
232
+ * List workflows you created (convenience for `listWorkflows({ creator: yourWallet })`).
233
+ */
234
+ async listMyWorkflows(opts = {}) {
235
+ return this.listWorkflows({ ...opts, creator: address });
236
+ },
237
+ // ─── Workflow Versions ───
238
+ /**
239
+ * List all versions of a workflow (public, free).
240
+ */
241
+ async listWorkflowVersions(workflowId) {
242
+ const res = await fetch(`${baseUrl}/api/workflows/${workflowId}/versions`);
243
+ if (!res.ok)
244
+ throw new Error(`Failed to list versions: ${res.status}`);
245
+ return res.json();
246
+ },
247
+ /**
248
+ * Get a specific workflow version by versionId or numeric version.
249
+ */
250
+ async getWorkflowVersion(workflowId, versionIdOrNumber) {
251
+ const res = await fetch(`${baseUrl}/api/workflows/${workflowId}/versions/${versionIdOrNumber}`);
252
+ if (!res.ok)
253
+ throw new Error(`Failed to get version: ${res.status}`);
201
254
  return res.json();
202
255
  },
256
+ /**
257
+ * Create a new workflow version (creator only). Defaults to setting it as default.
258
+ * Omit fields to inherit from current workflow.
259
+ */
260
+ async createWorkflowVersion(workflowId, updates = {}) {
261
+ return authedJson(`${baseUrl}/api/workflows/${workflowId}/versions`, {
262
+ method: 'POST',
263
+ body: JSON.stringify(updates),
264
+ });
265
+ },
266
+ /**
267
+ * Set a version as the default (active) for a workflow. Also rolls the
268
+ * workflow's graph/price/splits back to that version's snapshot.
269
+ */
270
+ async setDefaultWorkflowVersion(workflowId, versionIdOrNumber) {
271
+ return authedJson(`${baseUrl}/api/workflows/${workflowId}/versions/${versionIdOrNumber}`, {
272
+ method: 'PUT',
273
+ body: JSON.stringify({ setDefault: true }),
274
+ });
275
+ },
276
+ async deleteWorkflowVersion(workflowId, versionIdOrNumber) {
277
+ return authedJson(`${baseUrl}/api/workflows/${workflowId}/versions/${versionIdOrNumber}`, { method: 'DELETE' });
278
+ },
203
279
  /**
204
280
  * Run a workflow. Handles x402 payment automatically.
205
281
  *
@@ -217,82 +293,50 @@ export function createClient(config) {
217
293
  * Check $APINOW token balance and factory access.
218
294
  */
219
295
  async factoryBalance() {
220
- const res = await fetch(`${baseUrl}/api/user-factory/check-balance`, {
221
- headers: { 'x-wallet-address': account.address },
222
- });
223
- if (!res.ok)
224
- throw new Error(`Failed to check balance: ${res.status}`);
225
- return res.json();
296
+ return authedJson(`${baseUrl}/api/user-factory/check-balance`);
226
297
  },
227
298
  /**
228
299
  * List your user-factory endpoints.
229
300
  */
230
301
  async factoryList() {
231
- const res = await fetch(`${baseUrl}/api/user-factory`, {
232
- headers: { 'x-wallet-address': account.address },
233
- });
234
- if (!res.ok)
235
- throw new Error(`Failed to list factory endpoints: ${res.status}`);
236
- return res.json();
302
+ return authedJson(`${baseUrl}/api/user-factory`);
237
303
  },
238
304
  /**
239
305
  * Generate endpoint config from a natural-language idea.
240
306
  */
241
307
  async factoryGenerate(idea) {
242
- const res = await fetch(`${baseUrl}/api/user-factory/generate`, {
308
+ return authedJson(`${baseUrl}/api/user-factory/generate`, {
243
309
  method: 'POST',
244
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
245
310
  body: JSON.stringify({ idea }),
246
311
  });
247
- if (!res.ok)
248
- throw new Error(`Failed to generate: ${res.status}`);
249
- return res.json();
250
312
  },
251
313
  /**
252
314
  * Create an LLM endpoint via user-factory.
253
315
  */
254
316
  async factoryCreate(config) {
255
- const res = await fetch(`${baseUrl}/api/user-factory`, {
317
+ return authedJson(`${baseUrl}/api/user-factory`, {
256
318
  method: 'POST',
257
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
258
319
  body: JSON.stringify(config),
259
320
  });
260
- if (!res.ok) {
261
- const text = await res.text();
262
- throw new Error(`Failed to create endpoint: ${res.status} ${text}`);
263
- }
264
- return res.json();
265
321
  },
266
322
  /**
267
323
  * Create a markup workflow wrapping an existing endpoint.
268
324
  * Optionally allocate a portion of the markup to a token buy split.
269
325
  */
270
326
  async factoryMarkup(opts) {
271
- const res = await fetch(`${baseUrl}/api/user-factory/markup`, {
327
+ return authedJson(`${baseUrl}/api/user-factory/markup`, {
272
328
  method: 'POST',
273
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
274
329
  body: JSON.stringify(opts),
275
330
  });
276
- if (!res.ok) {
277
- const text = await res.text();
278
- throw new Error(`Failed to create markup: ${res.status} ${text}`);
279
- }
280
- return res.json();
281
331
  },
282
332
  /**
283
333
  * Test-call an endpoint without payment (free, server-side LLM call).
284
334
  */
285
335
  async factoryTestCall(opts) {
286
- const res = await fetch(`${baseUrl}/api/user-factory/test-call`, {
336
+ return authedJson(`${baseUrl}/api/user-factory/test-call`, {
287
337
  method: 'POST',
288
- headers: { 'Content-Type': 'application/json', 'x-wallet-address': account.address },
289
338
  body: JSON.stringify(opts),
290
339
  });
291
- if (!res.ok) {
292
- const text = await res.text();
293
- throw new Error(`Failed to test call: ${res.status} ${text}`);
294
- }
295
- return res.json();
296
340
  },
297
341
  // ─── Factory Pipeline ───
298
342
  /**
@@ -352,7 +396,7 @@ export function createClient(config) {
352
396
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
353
397
  method: 'POST',
354
398
  headers: { 'Content-Type': 'application/json' },
355
- body: JSON.stringify({ ...opts, walletAddress: account.address }),
399
+ body: JSON.stringify({ ...opts, walletAddress: address }),
356
400
  });
357
401
  if (!res.ok) {
358
402
  const text = await res.text();
@@ -387,7 +431,7 @@ export function createClient(config) {
387
431
  * Check free-tier UI generation eligibility for a wallet.
388
432
  */
389
433
  async checkFreeUI() {
390
- const params = new URLSearchParams({ checkFree: '1', wallet: account.address });
434
+ const params = new URLSearchParams({ checkFree: '1', wallet: address });
391
435
  const res = await fetch(`${baseUrl}/api/ai/generate-ui?${params}`);
392
436
  if (!res.ok)
393
437
  throw new Error(`Failed to check free tier: ${res.status}`);
@@ -397,7 +441,7 @@ export function createClient(config) {
397
441
  * Like, dislike, or comment on a generated UI.
398
442
  */
399
443
  async reactToUI(id, action, comment) {
400
- const body = { id, action, wallet: account.address };
444
+ const body = { id, action, wallet: address };
401
445
  if (comment)
402
446
  body.comment = comment;
403
447
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
@@ -418,7 +462,7 @@ export function createClient(config) {
418
462
  const res = await fetch(`${baseUrl}/api/ai/generate-ui`, {
419
463
  method: 'DELETE',
420
464
  headers: { 'Content-Type': 'application/json' },
421
- body: JSON.stringify({ id, wallet: account.address }),
465
+ body: JSON.stringify({ id, wallet: address }),
422
466
  });
423
467
  if (!res.ok) {
424
468
  const text = await res.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apinow-sdk",
3
- "version": "0.24.0",
3
+ "version": "0.27.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",