@ledewire/browser 0.6.1 → 0.8.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/index.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * const lw = Ledewire.init({ apiKey: 'your_api_key' })
13
13
  * const state = await lw.checkout.state('content-id')
14
14
  * // state.checkout_state.next_required_action:
15
- * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'
15
+ * // 'authenticate' | 'fund_wallet' | 'purchase'
16
16
  * </script>
17
17
  * ```
18
18
  *
@@ -44,12 +44,16 @@ export declare type AuthenticationResponse = components['schemas']['Authenticati
44
44
  * or when a token refresh fails and re-authentication is required.
45
45
  */
46
46
  export declare class AuthError extends LedewireError {
47
- constructor(message: string, code?: number);
47
+ static readonly brand: string;
48
+ constructor(message: string, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
48
49
  }
49
50
 
50
51
  /** Request body for API key authentication (seller). */
51
52
  declare type AuthLoginApiKeyRequest = components['schemas']['AuthLoginApiKeyRequest'];
52
53
 
54
+ /** Request body for authenticating as a buyer using a named API key + secret. */
55
+ declare type AuthLoginBuyerApiKeyRequest = components['schemas']['AuthLoginBuyerApiKeyRequest'];
56
+
53
57
  /** Request body for buyer email/password login. */
54
58
  declare type AuthLoginEmailRequest = components['schemas']['AuthLoginEmailRequest'];
55
59
 
@@ -130,6 +134,26 @@ declare class BrowserAuthNamespace {
130
134
  * @returns The authentication token response.
131
135
  */
132
136
  loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse>;
137
+ /**
138
+ * Log in using a buyer API key and secret.
139
+ * Returns a buyer-scoped JWT, stored automatically after successful authentication.
140
+ *
141
+ * Primarily useful when building buyer-facing dashboards where the user
142
+ * has created a named API key and wishes to authenticate with it programmatically,
143
+ * or for agent workflows running inside a browser context.
144
+ *
145
+ * @param body - The buyer API key and secret.
146
+ * @returns The authentication token response.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * await lw.auth.loginWithBuyerApiKey({
151
+ * key: 'bktst_abc123',
152
+ * secret: 'deadbeef...',
153
+ * })
154
+ * ```
155
+ */
156
+ loginWithBuyerApiKey(body: AuthLoginBuyerApiKeyRequest): Promise<AuthenticationResponse>;
133
157
  /**
134
158
  * Request a password reset code to be sent to the buyer's email address.
135
159
  *
@@ -192,6 +216,8 @@ declare class BrowserClient {
192
216
  readonly content: BrowserContentNamespace;
193
217
  /** Seller operations: API key login, content list/search/get */
194
218
  readonly seller: BrowserSellerNamespace;
219
+ /** Authenticated buyer account: API key management */
220
+ readonly user: UserNamespace;
195
221
  /* Excluded from this release type: __constructor */
196
222
  }
197
223
 
@@ -266,8 +292,10 @@ declare class BrowserConfigNamespace {
266
292
  * @example
267
293
  * ```ts
268
294
  * const result = await lw.content.getWithAccess('content-id')
269
- * if (result.access_info.next_required_action === 'view_content') {
270
- * renderMarkdown(result.content_body ?? '')
295
+ * if (result.access_info.has_purchased) {
296
+ * // Note: single-use — a past purchase does not imply access. Buy again
297
+ * // via lw.purchases.create() to receive the content; the delivery comes
298
+ * // back directly in that response, not from this endpoint.
271
299
  * }
272
300
  * ```
273
301
  */
@@ -286,8 +314,10 @@ declare class BrowserContentNamespace {
286
314
  * @example
287
315
  * ```ts
288
316
  * const result = await lw.content.getWithAccess('article-123')
289
- * if (result.access_info.next_required_action === 'view_content') {
290
- * renderMarkdown(result.content_body ?? '')
317
+ * if (result.access_info.next_required_action === 'purchase') {
318
+ * // Buy (or re-buy) via lw.purchases.create() — the response carries
319
+ * // content_body / content_uri directly. has_purchased means only
320
+ * // "has ever bought" and does not by itself grant access.
291
321
  * }
292
322
  * ```
293
323
  */
@@ -297,6 +327,11 @@ declare class BrowserContentNamespace {
297
327
  /**
298
328
  * Buyer purchases namespace — create and retrieve content purchases.
299
329
  *
330
+ * **Single-use purchase model:** a completed purchase does not imply
331
+ * standing access. `has_purchased` on other endpoints means only "has ever
332
+ * bought" — it is not a re-access grant. Buying again is how the buyer
333
+ * receives the content a second time; see {@link BrowserPurchasesNamespace.create}.
334
+ *
300
335
  * Obtain via `lw.purchases` — do not construct directly.
301
336
  *
302
337
  * @example
@@ -309,10 +344,50 @@ declare class BrowserPurchasesNamespace {
309
344
  protected readonly http: HttpClient;
310
345
  constructor(http: HttpClient);
311
346
  /**
312
- * Completes a content purchase using the buyer's wallet balance.
347
+ * Completes a content purchase using the buyer's wallet balance and
348
+ * delivers the content in the same response.
349
+ *
350
+ * **This response is the only place the delivered content ever arrives.**
351
+ * For a deliverable LedeWire holds, `content_body` carries the full body as
352
+ * plain UTF-8 text — it is never base64-encoded on the wire, so no
353
+ * `atob()` or decoding step is needed. For a deliverable hosted elsewhere,
354
+ * `content_uri` carries its URL instead. Neither field is present on
355
+ * {@link BrowserPurchasesNamespace.get} or {@link BrowserPurchasesNamespace.list}
356
+ * — there is no route that serves the same content a second time — so
357
+ * persist `content_body` / `content_uri` immediately if you need it later.
358
+ * A past purchase does not grant re-access: buying the same content again
359
+ * is a new charge that delivers it again.
313
360
  *
314
361
  * @param body - The content ID and expected price in cents.
315
- * @returns The completed purchase record.
362
+ * @returns The completed purchase record, including the delivered content.
363
+ * @throws {SpendCapReachedError} `402` — the buyer's daily spend cap has
364
+ * been reached. There is no funding URL for this refusal: adding money
365
+ * to the wallet does not clear it. It clears only when the spend window
366
+ * rolls over at `resetsAt`, or when the cap is raised via
367
+ * `lw.user.spendCap.update()`.
368
+ * @throws {NotFoundError} `404` — the content does not exist, or its
369
+ * visibility is `unlisted` (non-public) and this buyer has no standing
370
+ * grant to see it. Non-public content is not purchasable through this
371
+ * endpoint.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * const purchase = await lw.purchases.create({ content_id: 'content-id' })
376
+ * await saveDeliveredContent(purchase) // persist now — one-time delivery
377
+ *
378
+ * if (purchase.content_body && purchase.content.content_type === 'markdown') {
379
+ * renderMarkdown(purchase.content_body)
380
+ * } else if (purchase.content_body && purchase.content.content_type === 'html') {
381
+ * // Inline HTML is seller-supplied — sanitise before inserting it.
382
+ * container.innerHTML = DOMPurify.sanitize(purchase.content_body)
383
+ * } else if (purchase.content_uri) {
384
+ * // Seller-supplied URI — check the scheme before navigating to it.
385
+ * const uri = new URL(purchase.content_uri)
386
+ * if (['https:', 'http:'].includes(uri.protocol)) {
387
+ * window.location.href = purchase.content_uri
388
+ * }
389
+ * }
390
+ * ```
316
391
  */
317
392
  create(body: PurchaseCreateRequest): Promise<PurchaseResponse>;
318
393
  /**
@@ -457,12 +532,33 @@ declare class BrowserWalletNamespace {
457
532
  /**
458
533
  * Returns the authenticated buyer's current wallet balance.
459
534
  *
460
- * @returns The current wallet balance in cents.
535
+ * `balance_cents` and `spendable_cents` are always the same number —
536
+ * `balance_cents` has always meant "what you can spend," and money committed to
537
+ * a bulk acquisition is a hold, which moves it out of the wallet rather than
538
+ * annotating it. `held_cents` is the total currently committed to active bulk
539
+ * acquisitions and not yet spent or released, and `holds` lists one entry per
540
+ * such acquisition (`acquisition_id`, `held_cents`, `authorized_at`) so a buyer
541
+ * mid-acquisition can see why their balance is lower than their purchase
542
+ * history explains.
543
+ *
544
+ * @returns The current wallet balance in cents, including held funds detail.
461
545
  */
462
546
  balance(): Promise<WalletBalanceResponse>;
463
547
  /**
464
548
  * Returns the authenticated buyer's wallet transaction history, newest first.
465
549
  *
550
+ * `bulk_acquisition` and `bulk_hold` are each a single entry for a whole bulk
551
+ * acquisition — its per-work purchases are deliberately not listed here,
552
+ * because one acquisition can hold tens of thousands of them and they describe
553
+ * one decision. A `bulk_hold` entry is an acquisition still holding funds (money
554
+ * left the wallet but has not been spent); it becomes a `bulk_acquisition` entry
555
+ * for the amount actually captured once the acquisition ends, and an
556
+ * acquisition never produces both. Note that a bulk acquisition's per-work
557
+ * purchases each carry a display `price_cents` that is **not summable** — bulk
558
+ * prices at micro precision and rounds to cents once, on the acquisition total,
559
+ * so `amount_cents` on the `bulk_acquisition` / `bulk_hold` entry is the figure
560
+ * to read.
561
+ *
466
562
  * @returns A list of completed wallet transaction entries.
467
563
  */
468
564
  transactions(): Promise<WalletTransactionItem[]>;
@@ -491,7 +587,12 @@ declare class BrowserWalletNamespace {
491
587
  * ```ts
492
588
  * const state = await lw.checkout.state('content-id')
493
589
  * // state.checkout_state.next_required_action:
494
- * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'
590
+ * // 'authenticate' | 'fund_wallet' | 'purchase'
591
+ * //
592
+ * // Single-use model: a completed purchase does not imply access, so
593
+ * // 'purchase' can still be the next action even when has_purchased is
594
+ * // true. Buying delivers the content directly in the POST /v1/purchases
595
+ * // response — there is no separate "view content" step.
495
596
  * ```
496
597
  */
497
598
  declare class CheckoutNamespace {
@@ -512,11 +613,18 @@ declare class CheckoutNamespace {
512
613
  }
513
614
 
514
615
  /**
515
- * Next step in a content checkout flow.
516
- * Extends `NextRequiredAction` with the terminal `view_content` state
517
- * (returned once the buyer has purchased and can view the content).
616
+ * Next step in a content checkout flow. Consumer-facing alias for
617
+ * `CheckoutStateResponse['checkout_state']['next_required_action']`.
618
+ *
619
+ * **Single-use purchase model:** there is no terminal "you have access" state.
620
+ * A completed purchase does not imply access — `has_purchased` means only "has ever
621
+ * bought" — so `next_required_action` can still read `'purchase'` for content the
622
+ * buyer already bought once. Buying again is how the buyer receives the content:
623
+ * the delivery (`content_body` / `content_uri`) is returned directly in the
624
+ * response to `POST /v1/purchases`, and nowhere else — not on `GET`/list, and not
625
+ * via a since-withdrawn `'view_content'` state.
518
626
  */
519
- export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
627
+ export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase';
520
628
 
521
629
  /**
522
630
  * Checkout state machine result for a specific content item, as returned by
@@ -548,7 +656,7 @@ declare interface components {
548
656
  has_sufficient_funds: boolean;
549
657
  wallet_balance_cents: number;
550
658
  /** @enum {string} */
551
- next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'none';
659
+ next_required_action: 'authenticate' | 'fund_wallet' | 'purchase';
552
660
  };
553
661
  AuthenticationResponse: {
554
662
  /** @enum {string} */
@@ -621,6 +729,11 @@ declare interface components {
621
729
  error: {
622
730
  code: number;
623
731
  message: string;
732
+ /**
733
+ * @description Machine-readable reason, present on refusals that carry one. Branch on this rather than on `message`, which is prose and may be reworded. `retrieval_failed` is transient and worth retrying; `not_licensable` means report it undelivered; `price_drifted` means re-quote; `client_error` is ours to fix and must never be retried unchanged; `insufficient_funds` is cleared by funding the wallet and `daily_spend_cap_reached` deliberately is not.
734
+ * @enum {string}
735
+ */
736
+ type?: 'retrieval_failed' | 'not_licensable' | 'price_drifted' | 'client_error' | 'insufficient_funds' | 'daily_spend_cap_reached';
624
737
  };
625
738
  };
626
739
  AuthSignupRequest: {
@@ -646,6 +759,430 @@ declare interface components {
646
759
  key: string;
647
760
  secret?: string;
648
761
  };
762
+ AuthLoginBuyerApiKeyRequest: {
763
+ /** @description Structured buyer API key (e.g. bktst_abc123) */
764
+ key: string;
765
+ /** @description 64-char hex secret, shown once at creation */
766
+ secret: string;
767
+ };
768
+ UserApiKey: {
769
+ /** Format: uuid */
770
+ id: string;
771
+ name: string;
772
+ /** @description Structured public identifier (e.g. bktst_abc123) */
773
+ key: string;
774
+ /** Format: date-time */
775
+ last_used_at?: string | null;
776
+ /** @description Maximum cumulative spend in cents. null = no limit. */
777
+ spending_limit_cents?: number | null;
778
+ /** Format: date-time */
779
+ created_at: string;
780
+ };
781
+ UserApiKeyCreateRequest: {
782
+ /** @description Human-readable label for the key */
783
+ name: string;
784
+ /** @description Optional spend ceiling in cents */
785
+ spending_limit_cents?: number | null;
786
+ };
787
+ /** @description Returned once only at creation. The secret is not stored and cannot be retrieved again. */
788
+ UserApiKeyCreateResponse: {
789
+ /** Format: uuid */
790
+ id: string;
791
+ /** @description Structured public identifier (e.g. bktst_abc123) */
792
+ key: string;
793
+ /** @description 64-char hex authentication secret. Store immediately — shown once only. */
794
+ secret: string;
795
+ };
796
+ /** @description The authenticated buyer's daily spend cap, read against the current spend window. The cap governs every wallet debit the buyer makes — MCP, REST, or the web payment gate — and spend is derived from completed purchases, so a refund returns allowance. cap_cents, spent_cents, remaining_cents and resets_at are spelled exactly as they are in DailySpendCapReachedError, so a refusal and this resource describe the same numbers. */
797
+ UserSpendCap: {
798
+ /** @description The daily spend cap in cents. null means uncapped. */
799
+ cap_cents: number | null;
800
+ /** @description IANA timezone whose calendar day bounds the spend window. Defaults to UTC. Reports the zone actually used, so an unrecognised stored value reads back as UTC. */
801
+ spend_window_timezone: string;
802
+ /** @description Total spent so far in the current spend window, summed over completed purchases by any path. */
803
+ spent_cents: number;
804
+ /** @description Cap minus spend so far, floored at zero. null when the buyer is uncapped — there is no ceiling to subtract from. Zero after the cap is lowered below spend already made. */
805
+ remaining_cents: number | null;
806
+ /**
807
+ * Format: date-time
808
+ * @description The instant the current spend window rolls, in UTC.
809
+ */
810
+ resets_at: string;
811
+ /** @description Whether this buyer's bulk acquisitions are exempt from the cap. When true, spent_cents and remaining_cents describe ordinary spend only — an authorized bulk acquisition does not consume them — so a client presenting remaining_cents must say which number it is. */
812
+ bulk_exempt: boolean;
813
+ };
814
+ /** @description Sets the buyer's cap. The field is required and nullable: null is how a buyer becomes uncapped, and an omitted field is treated as a client error rather than as a request to be uncapped. A cap below spend already made in the current window is accepted — it simply leaves remaining_cents at zero until the window rolls. Zero is a valid cap and refuses every priced purchase. */
815
+ UserSpendCapUpdateRequest: {
816
+ /** @description New cap in whole cents, or null to remove the cap. Negative and fractional values are rejected with 422. */
817
+ daily_spend_limit_cents: number | null;
818
+ };
819
+ McpApiKeyCreateRequest: {
820
+ /** @description Human-readable name for the key */
821
+ label: string;
822
+ /** @description Grants access to search and read-only MCP tools. Defaults to true. */
823
+ can_search?: boolean;
824
+ /** @description Grants access to payment submission via MCP. Defaults to false. */
825
+ can_purchase?: boolean;
826
+ /**
827
+ * Format: uuid
828
+ * @description Scopes the key to a specific store. User must be an owner or author of that store.
829
+ */
830
+ store_id?: string | null;
831
+ /** @description Grants access to seller content management tools. Defaults to false. */
832
+ can_manage_content?: boolean;
833
+ /** @description Grants access to seller analytics tools. Defaults to false. */
834
+ can_read_analytics?: boolean;
835
+ };
836
+ /** @description Returned once only at creation. The secret cannot be retrieved again. */
837
+ McpApiKeyCreateResponse: {
838
+ /** Format: uuid */
839
+ id: string;
840
+ /** @description Public identifier used to look up the key */
841
+ key: string;
842
+ /** @description Authentication secret. Store immediately — shown once only. */
843
+ secret: string;
844
+ label: string;
845
+ can_search: boolean;
846
+ can_purchase: boolean;
847
+ /** Format: uuid */
848
+ store_id?: string | null;
849
+ can_manage_content: boolean;
850
+ can_read_analytics: boolean;
851
+ };
852
+ McpApiKey: {
853
+ /** Format: uuid */
854
+ id: string;
855
+ label: string;
856
+ key: string;
857
+ can_search: boolean;
858
+ can_purchase: boolean;
859
+ /** Format: uuid */
860
+ store_id?: string | null;
861
+ can_manage_content: boolean;
862
+ can_read_analytics: boolean;
863
+ /** Format: date-time */
864
+ last_used_at?: string | null;
865
+ /** Format: date-time */
866
+ created_at: string;
867
+ };
868
+ McpContentSearchResult: {
869
+ /** Format: uuid */
870
+ id: string;
871
+ title: string;
872
+ /** @description Plain UTF-8 text preview. Never base64. */
873
+ teaser: string;
874
+ price_cents: number;
875
+ /** @enum {string} */
876
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
877
+ /** Format: uuid */
878
+ store_id: string;
879
+ store_name: string;
880
+ tags: string[];
881
+ /** @description Canonical article URL. Populated for Tollbit results; null for Ledewire items unless resource_url is set. */
882
+ url?: string | null;
883
+ /** @description Academic author display names. Null for non-academic content or unknown values. */
884
+ authors: string[] | null;
885
+ /** @description Academic abstract text. Null for non-academic content or when unavailable. */
886
+ abstract: string | null;
887
+ /** @description DOI URL for academic works. Null for non-academic content or unknown values. */
888
+ doi: string | null;
889
+ /** @description Academic publication date. Null for non-academic content or unknown values. */
890
+ publication_date: string | null;
891
+ /** @description Academic citation count from source metadata. Null for non-academic content or unknown values. */
892
+ citation_count: number | null;
893
+ };
894
+ /** @description Content returned to a store team member (owner or author) accessing their own content without a purchase. */
895
+ McpSellerContent: components['schemas']['McpContentSearchResult'] & {
896
+ /** @description Article body. Present when content_type is `markdown` or inline `html`. */
897
+ content_body?: string | null;
898
+ /** @description External content URI. Present when content_type is `external_ref`, `pdf`, `image`, `video`, or remote `html`. */
899
+ content_uri?: string | null;
900
+ };
901
+ McpFullContent: components['schemas']['McpContentSearchResult'] & {
902
+ /** @description Article body. Present when content_type is `markdown` or inline `html`. */
903
+ content_body?: string | null;
904
+ /** @description External content URI. Present when content_type is `external_ref`, `pdf`, `image`, `video`, or remote `html`. */
905
+ content_uri?: string | null;
906
+ /** Format: date-time */
907
+ purchased_at: string;
908
+ /** @description The licence the broker granted for this work, as returned on the retrieval — not the one we requested. Null when no grant was recorded: every non-brokered purchase, and brokered purchases made before the grant was captured. Null means "not recorded", never "no rights granted". */
909
+ license?: {
910
+ /**
911
+ * @description The broker's licence type, e.g. `ON_DEMAND_LICENSE` or `ON_DEMAND_FULL_USE_LICENSE`. Which one is minted depends on the rates the property publishes, so it varies per work.
912
+ * @example ON_DEMAND_LICENSE
913
+ */
914
+ type: string;
915
+ /**
916
+ * @description The rights granted. `ON_DEMAND_LICENSE` carries `PARTIAL_USE` alone; `ON_DEMAND_FULL_USE_LICENSE` carries `FULL_USE` and `PARTIAL_USE`.
917
+ * @example [
918
+ * "PARTIAL_USE"
919
+ * ]
920
+ */
921
+ permissions: string[];
922
+ /** @description The broker-hosted licence document for this grant. */
923
+ document_url: string | null;
924
+ } | null;
925
+ _meta: {
926
+ 'x402/payment-response': {
927
+ success: boolean;
928
+ transaction: string;
929
+ network: string;
930
+ payer: string;
931
+ accessToken: string;
932
+ };
933
+ };
934
+ };
935
+ /** @description A record that a purchase happened: what was bought, when, at what price, and under which licence. Deliberately **not** a content schema. `list_purchases` used to return `McpFullContent`, which carries the article body, so a Buyer could re-read the full text of every work they had ever bought, free, 20 per call. Under single use (#939) a purchase is one licensed delivery, so purchase history is a receipt and the body is not part of it. Use `get_content` to buy a work — including one bought before. */
936
+ McpPurchaseReceipt: {
937
+ /** @description The purchase this receipt records. Distinct per purchase, so repeat buys of one work are distinguishable. */
938
+ purchase_id: string;
939
+ content_id: string;
940
+ title: string;
941
+ /** @description Where the work lives. Not a way to read it — a brokered URL serves a paywall to an unlicensed caller. */
942
+ url?: string | null;
943
+ store_id: string;
944
+ store_name: string;
945
+ /** @description What this purchase was charged, at the price in force when it was made. */
946
+ price_cents: number;
947
+ /** Format: date-time */
948
+ purchased_at: string;
949
+ /** @description The licence the broker granted for this purchase, as returned on the retrieval. Null when no grant was recorded: every non-brokered purchase, and brokered purchases made before the grant was captured. Null means "not recorded", never "no rights granted". */
950
+ license?: {
951
+ /** @example ON_DEMAND_LICENSE */
952
+ type: string;
953
+ /**
954
+ * @example [
955
+ * "PARTIAL_USE"
956
+ * ]
957
+ */
958
+ permissions: string[];
959
+ document_url: string | null;
960
+ } | null;
961
+ };
962
+ McpGetWalletBalanceResult: {
963
+ /** @description Current wallet balance in cents for the authenticated MCP key owner. */
964
+ wallet_balance_cents: number;
965
+ };
966
+ McpRegisterResult: {
967
+ /** @description The API key identifier (not secret). */
968
+ key: string;
969
+ /** @description The API key secret — returned once, never retrievable again. */
970
+ secret: string;
971
+ label: string;
972
+ can_search: boolean;
973
+ can_purchase: boolean;
974
+ };
975
+ McpGetContentDetailsResult: {
976
+ content: components['schemas']['McpContentSearchResult'];
977
+ /** @description Whether the authenticated user has a completed purchase for this content. */
978
+ has_purchased: boolean;
979
+ /** @description Current wallet balance in cents for the authenticated MCP key owner. */
980
+ wallet_balance_cents: number;
981
+ };
982
+ McpListPurchasesResult: {
983
+ /** @description Receipts, newest first, one per purchase and never collapsed — a Buyer may hold several purchases of one work, each at the price in force when it was made. These were `McpFullContent` until #939, which meant this endpoint returned the body of every article the Buyer had ever bought. */
984
+ purchases: components['schemas']['McpPurchaseReceipt'][];
985
+ /** @description Total number of completed purchases. */
986
+ total_count: number;
987
+ offset: number;
988
+ limit: number;
989
+ };
990
+ /** @description Returned as structuredContent when a get_content payment attempt fails due to insufficient wallet balance. Always accompanied by isError: true. */
991
+ McpInsufficientFundsError: {
992
+ /** @enum {string} */
993
+ error: 'insufficient_funds';
994
+ /** @description Current wallet balance in cents. */
995
+ wallet_balance_cents: number;
996
+ /** @description Price of the content in cents. */
997
+ required_cents: number;
998
+ /** @description Amount needed to top up the wallet to cover this purchase (required_cents - wallet_balance_cents). */
999
+ shortfall_cents: number;
1000
+ /**
1001
+ * Format: uri
1002
+ * @description Direct link to the Ledewire wallet funding page, pre-filled with the shortfall amount.
1003
+ */
1004
+ funding_url: string;
1005
+ };
1006
+ /** @description Returned as structuredContent when a get_content call is refused because the buyer's daily spend cap would be exceeded. Always accompanied by isError: true. Deliberately carries no funding_url — adding money to the wallet cannot raise a cap, and a payload resembling McpInsufficientFundsError would send agents to the wrong remedy. The cap resets at resets_at; until then the only remedies are raising the cap or waiting. */
1007
+ McpDailySpendCapReachedError: {
1008
+ /** @enum {string} */
1009
+ error: 'daily_spend_cap_reached';
1010
+ /** @description The buyer's daily spend cap in cents. */
1011
+ cap_cents: number;
1012
+ /** @description Total spent so far in the current spend window. Ordinary completed purchases are summed per row; a bulk acquisition contributes its total, never its per-work purchases. */
1013
+ spent_cents: number;
1014
+ /** @description Cap minus spend so far, floored at zero — what the buyer may still spend in this window. */
1015
+ remaining_cents: number;
1016
+ /**
1017
+ * Format: date-time
1018
+ * @description The instant the current spend window rolls, in UTC. One calendar day boundary in the buyer's own timezone.
1019
+ */
1020
+ resets_at: string;
1021
+ /** @description Whether this buyer's bulk acquisitions are exempt from the cap. When true, spent_cents and remaining_cents describe ordinary spend only — an authorized bulk acquisition does not consume them — so a client presenting remaining_cents must say which number it is. */
1022
+ bulk_exempt: boolean;
1023
+ };
1024
+ /** @description The REST and x402 web-gate form of the same refusal, returned with HTTP 402 Payment Required by POST /v1/purchases and GET /v1/x402/contents/{id}. Carries the same fields as McpDailySpendCapReachedError alongside the standard error envelope, and the same absence of a funding URL. `error.type` is the machine-readable discriminator. */
1025
+ DailySpendCapReachedError: {
1026
+ error: {
1027
+ /** @enum {integer} */
1028
+ code: 402;
1029
+ message: string;
1030
+ /** @enum {string} */
1031
+ type: 'daily_spend_cap_reached';
1032
+ };
1033
+ /** @description The buyer's daily spend cap in cents. */
1034
+ cap_cents: number;
1035
+ /** @description Total spent so far in the current spend window, summed over completed purchases by any path. */
1036
+ spent_cents: number;
1037
+ /** @description Cap minus spend so far, floored at zero — what the buyer may still spend in this window. */
1038
+ remaining_cents: number;
1039
+ /**
1040
+ * Format: date-time
1041
+ * @description The instant the current spend window rolls, in UTC.
1042
+ */
1043
+ resets_at: string;
1044
+ /** @description Whether this buyer's bulk acquisitions are exempt from the cap. When true, spent_cents and remaining_cents describe ordinary spend only. */
1045
+ bulk_exempt: boolean;
1046
+ };
1047
+ /** @description Returned by the fund_wallet tool. Contains the buyer portal wallet URL. Open this link in a browser to add funds using the Ledewire wallet funding page. */
1048
+ McpFundWalletResult: {
1049
+ /**
1050
+ * Format: uri
1051
+ * @description Buyer portal wallet URL. Includes an `amount` query param when amount_cents was provided.
1052
+ */
1053
+ url: string;
1054
+ };
1055
+ /** @description One JSON-RPC 2.0 request envelope. `id` is omitted for notifications (`notifications/initialized`), which are acknowledged with HTTP 202 and no body. */
1056
+ McpJsonRpcRequest: {
1057
+ /** @enum {string} */
1058
+ jsonrpc: '2.0';
1059
+ /** @enum {string} */
1060
+ method: 'initialize' | 'notifications/initialized' | 'ping' | 'tools/list' | 'tools/call' | 'resources/list' | 'resources/read' | 'prompts/list' | 'prompts/get';
1061
+ /** @description Client-supplied request identifier. Echoed back in the response. Omitted for notifications. */
1062
+ id?: number | string;
1063
+ /** @description Shape depends on `method`. `initialize`: `{ protocolVersion, capabilities, clientInfo }` — all three required. `tools/call`: `{ name: "<tool_name>", arguments: { ... } }`; call `tools/list` for each tool's full `inputSchema`. `resources/read`: `{ uri: "ledewire://..." }`. `prompts/get`: `{ name: "<prompt_name>", arguments: { ... } }`. `tools/list`, `resources/list`, `prompts/list` and `ping` take no parameters. */
1064
+ params?: {
1065
+ /**
1066
+ * @description Tool name for `tools/call`, prompt name for `prompts/get`.
1067
+ * @enum {string}
1068
+ */
1069
+ name?: 'search_content' | 'list_publications' | 'list_publication_works' | 'register' | 'get_wallet_balance' | 'get_content_details' | 'get_content' | 'list_purchases' | 'list_my_stores' | 'fund_wallet' | 'list_my_content' | 'search_my_content' | 'register_content' | 'update_content' | 'check_content_status' | 'get_sales_summary' | 'get_content_sales' | 'get_buyer_insights' | 'onboarding_checklist' | 'troubleshoot_integration' | 'optimize_pricing';
1070
+ arguments?: {
1071
+ [key: string]: unknown;
1072
+ };
1073
+ /**
1074
+ * @description Resource URI for `resources/read`.
1075
+ * @enum {string}
1076
+ */
1077
+ uri?: 'ledewire://docs/overview' | 'ledewire://docs/pricing-guidelines' | 'ledewire://seller/context';
1078
+ /** @description Requested revision for `initialize`. See the negotiation note on the endpoint. */
1079
+ protocolVersion?: string;
1080
+ capabilities?: Record<string, never>;
1081
+ clientInfo?: Record<string, never>;
1082
+ };
1083
+ };
1084
+ /** @description Returned by the list_my_stores tool. Every store the authenticated key's owner holds owner or author access to, with that store's non-archived content count. */
1085
+ McpListMyStoresResult: {
1086
+ stores: {
1087
+ /** Format: uuid */
1088
+ store_id: string;
1089
+ store_name: string;
1090
+ /**
1091
+ * @description Highest access the key owner holds on this store.
1092
+ * @enum {string}
1093
+ */
1094
+ role: 'owner' | 'author';
1095
+ /** @description Non-archived content items in the store. */
1096
+ content_count: number;
1097
+ }[];
1098
+ };
1099
+ /** @description Returned by the list_my_content tool. A page of the connected store's catalog, including items no buyer can see. Pagination follows the standard `offset`/`limit` contract shared with search_content and list_purchases; the `total`, `page` and `per_page` fields are retained for clients written against the tool's original contract and describe the same window. */
1100
+ McpListMyContentResult: {
1101
+ contents: {
1102
+ /** Format: uuid */
1103
+ id: string;
1104
+ title: string;
1105
+ teaser?: string | null;
1106
+ price_cents: number;
1107
+ /** @description `public`, `unlisted`, or `archived`. */
1108
+ visibility: string;
1109
+ /** @enum {string} */
1110
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1111
+ tags: string[];
1112
+ /** Format: date-time */
1113
+ created_at: string;
1114
+ }[];
1115
+ /** @description Total matching items across all pages, before pagination. */
1116
+ total_count: number;
1117
+ /** @description Number of records skipped to produce this page. */
1118
+ offset: number;
1119
+ /** @description Maximum records this page could contain. */
1120
+ limit: number;
1121
+ /** @description Legacy, superseded by `total_count`. Identical value, retained for existing clients. */
1122
+ total: number;
1123
+ /** @description Legacy, superseded by `offset`. One-based page number of the page *containing* the first record returned, derived from the window actually applied. When `offset` is not a multiple of `limit` there is no exact page number and this page's own span only overlaps the records returned — `offset: 25, limit: 10` returns records 25-34 and reports page 3, which spans 20-29. Paginate by `offset`, not by incrementing this field. */
1124
+ page: number;
1125
+ /** @description Legacy, superseded by `limit`. Identical value, retained for existing clients. */
1126
+ per_page: number;
1127
+ };
1128
+ /** @description Returned by the search_my_content tool. Deliberately narrower than McpListMyContentResult — it is a lookup aid for finding a content_id, not a catalog view. */
1129
+ McpSearchMyContentResult: {
1130
+ contents: {
1131
+ /** Format: uuid */
1132
+ id: string;
1133
+ title: string;
1134
+ teaser?: string | null;
1135
+ /** @enum {string} */
1136
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1137
+ tags: string[];
1138
+ }[];
1139
+ };
1140
+ /** @description Returned by both register_content and update_content on success. The two tools share one shape: the identifying fields of the content item as it now stands. Neither returns the body — read it back with get_content if you need it. */
1141
+ McpContentMutationResult: {
1142
+ /** Format: uuid */
1143
+ id?: string;
1144
+ title?: string;
1145
+ /** @enum {string} */
1146
+ content_type?: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1147
+ /** @description `public`, `unlisted`, or `archived`. */
1148
+ visibility?: string;
1149
+ price_cents?: number;
1150
+ };
1151
+ /** @description Returned by the check_content_status tool. Read-only diagnosis of one content item — `issues` is empty exactly when `is_healthy` is true. */
1152
+ McpCheckContentStatusResult: {
1153
+ /** Format: uuid */
1154
+ content_id: string;
1155
+ title: string;
1156
+ /** @description Problems found, in plain English. Empty when the item is healthy. */
1157
+ issues: string[];
1158
+ /** @description Suggested next steps, one per issue where a fix is known. */
1159
+ recommendations: string[];
1160
+ is_healthy: boolean;
1161
+ };
1162
+ /** @description One row of the get_content_sales tool's result — a single content item's sales performance. */
1163
+ McpContentSalesRow: {
1164
+ /** Format: uuid */
1165
+ content_id: string;
1166
+ title: string;
1167
+ purchase_count: number;
1168
+ revenue_cents: number;
1169
+ };
1170
+ /** @description Result of the get_content_sales tool — one page of the store's per-content sales ranking. Carried identically in structuredContent and in the double-encoded text block. */
1171
+ McpGetContentSalesResult: {
1172
+ content_sales: components['schemas']['McpContentSalesRow'][];
1173
+ /** @description Number of content items in the whole ranking, not in the returned page. Scoped the same way the rows are — an author key counts only their attributed content. */
1174
+ total_count: number;
1175
+ offset: number;
1176
+ limit: number;
1177
+ };
1178
+ /** @description Result of the get_buyer_insights tool — one page of the store's buyers, ranked by total spend descending. Carried identically in structuredContent and in the text block. */
1179
+ McpGetBuyerInsightsResult: {
1180
+ buyer_insights: components['schemas']['BuyerStatisticsItem'][];
1181
+ /** @description Number of buyers in the whole ranking, not in the returned page. Scoped the same way the rows are — an author key counts only buyers of their attributed content. */
1182
+ total_count: number;
1183
+ offset: number;
1184
+ limit: number;
1185
+ };
649
1186
  AuthTokenRefreshRequest: {
650
1187
  refresh_token?: string;
651
1188
  };
@@ -735,8 +1272,26 @@ declare interface components {
735
1272
  [key: string]: unknown;
736
1273
  };
737
1274
  };
1275
+ /** @description The buyer's wallet. balance_cents and spendable_cents are the same number and always will be — balance_cents has always meant "what you can spend", and money committed to a bulk acquisition is a hold, which moves it out of the wallet rather than annotating it. held_cents and holds exist so a buyer mid-acquisition can see why their balance is lower than their purchase history explains. */
738
1276
  WalletBalanceResponse: {
1277
+ /** @description Spendable balance in cents. Excludes funds held against a bulk acquisition. */
739
1278
  balance_cents: number;
1279
+ /** @description The same figure as balance_cents, named in the vocabulary holds require. */
1280
+ spendable_cents: number;
1281
+ /** @description Total committed to active bulk acquisitions and not yet spent or released. */
1282
+ held_cents: number;
1283
+ /** @description One entry per bulk acquisition currently holding funds. Empty when none is. */
1284
+ holds: {
1285
+ /** Format: uuid */
1286
+ acquisition_id: string;
1287
+ /** @description What this acquisition is currently holding. */
1288
+ held_cents: number;
1289
+ /**
1290
+ * Format: date-time
1291
+ * @description When the hold was placed.
1292
+ */
1293
+ authorized_at: string;
1294
+ }[];
740
1295
  };
741
1296
  WalletTransactionItem: {
742
1297
  /** @description ID of the transaction entry (matches the source record) */
@@ -747,17 +1302,20 @@ declare interface components {
747
1302
  */
748
1303
  type: 'credit' | 'debit';
749
1304
  /**
750
- * @description What caused this wallet movement
1305
+ * @description What caused this wallet movement. bulk_acquisition and bulk_hold are each one entry for a whole bulk acquisition — its per-work purchases are deliberately not listed here, because one acquisition can hold tens of thousands of them and they describe one decision. bulk_hold is an acquisition still holding funds: the money has left the wallet but has not been spent, and it becomes a bulk_acquisition entry for the amount actually captured once the acquisition ends. An acquisition never produces both.
751
1306
  * @enum {string}
752
1307
  */
753
- reason: 'wallet_funding' | 'purchase' | 'refund';
754
- /** @description Always positive; direction expressed by `type` */
1308
+ reason: 'wallet_funding' | 'purchase' | 'refund' | 'bulk_acquisition' | 'bulk_hold';
1309
+ /** @description Always positive; direction expressed by `type`. For a bulk_hold entry this is what is currently held; for a bulk_acquisition entry it is what was captured, not what was held — the uncaptured remainder was released, never charged. Note that a bulk acquisition's per-work purchases each carry a price_cents that is a rounded display value and is NOT summable: bulk prices at micro precision and rounds to cents once, on the acquisition total, so this aggregate is the figure to read. */
755
1310
  amount_cents: number;
756
1311
  /** @description Running wallet balance immediately after this event */
757
1312
  balance_after_cents: number;
758
- /** @enum {string} */
759
- status: 'completed' | 'pending' | 'failed' | 'cancelled';
760
- /** @description ID of the source record (Purchase or FundingTransfer) */
1313
+ /**
1314
+ * @description Status of the source record. authorized/acquiring/settled/cancelled/failed are bulk acquisition states; the rest are purchase and funding transfer states.
1315
+ * @enum {string}
1316
+ */
1317
+ status: 'completed' | 'pending' | 'failed' | 'cancelled' | 'settled' | 'reverted' | 'refunded' | 'authorized' | 'acquiring';
1318
+ /** @description ID of the source record (Purchase, FundingTransfer or Acquisition) */
761
1319
  reference_id: string;
762
1320
  /** @description Human-readable label suitable for display */
763
1321
  description: string;
@@ -783,17 +1341,17 @@ declare interface components {
783
1341
  has_sufficient_funds?: boolean;
784
1342
  };
785
1343
  };
786
- /** @description Update request for content. `content_body` applies to `markdown` content; `content_uri` and `external_identifier` apply to `external_ref` content. */
1344
+ /** @description Update request for content. `content_body` applies to `markdown` and inline `html` content; `content_uri` applies to `external_ref` and remote `html` content. For `html`, submitting both `content_body` and `content_uri` in the same request is rejected with `400`. */
787
1345
  ContentUpdateRequest: {
788
1346
  /** @description Content title */
789
1347
  title?: string;
790
1348
  /**
791
1349
  * Format: byte
792
- * @description Full article body in markdown, base64 encoded. For `markdown` content only.
1350
+ * @description Full article body, base64 encoded. For `markdown` and inline `html` content. Must be base64-encoded before sending (e.g. `btoa(bodyText)`).
793
1351
  */
794
1352
  content_body?: string;
795
1353
  /**
796
- * @description URI of the resource. Required for `external_ref` content; optional for `markdown` content.
1354
+ * @description URI of the resource. For `external_ref` and remote `html` content.
797
1355
  * @example https://vimeo.com/123456789
798
1356
  */
799
1357
  content_uri?: string;
@@ -804,7 +1362,7 @@ declare interface components {
804
1362
  external_identifier?: string | null;
805
1363
  /**
806
1364
  * Format: byte
807
- * @description Content teaser, base64 encoded
1365
+ * @description Content teaser, base64 encoded. Must be base64-encoded before sending (e.g. `btoa(teaserText)`)
808
1366
  */
809
1367
  teaser?: string;
810
1368
  /** @description Price in cents (must be greater than 0) */
@@ -1025,22 +1583,22 @@ declare interface components {
1025
1583
  */
1026
1584
  started_at?: string;
1027
1585
  };
1028
- /** @description Create request body for content. Required fields vary by `content_type`: `markdown` requires `content_body`; `external_ref` requires `content_uri`. Both types accept an optional `content_uri` link. */
1586
+ /** @description Create request body for content. Required fields vary by `content_type`: `markdown` requires `content_body`; `html` requires exactly one of `content_body` (inline) or `content_uri` (remote); `external_ref`, `pdf`, `image`, and `video` require `content_uri` (`content_body` is not accepted). */
1029
1587
  Content: {
1030
1588
  /**
1031
- * @description The type of content being created.
1589
+ * @description The type of content being created. `pdf`, `image`, and `video` require `content_uri`; `content_body` is not accepted for these types.
1032
1590
  * @enum {string}
1033
1591
  */
1034
- content_type: 'markdown' | 'external_ref';
1592
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref';
1035
1593
  /** @description Content title */
1036
1594
  title: string;
1037
1595
  /**
1038
1596
  * Format: byte
1039
- * @description Full article body in markdown, base64 encoded. Required when `content_type` is `markdown`.
1597
+ * @description Full article body, base64 encoded. Required when `content_type` is `markdown`. For `html` content, provide either `content_body` (inline) or `content_uri` (remote) — not both. Must be base64-encoded before sending (e.g. `btoa(htmlText)`). Note: HTML special characters (`<`, `>`, `&`) should be unicode-escaped when embedding the JSON response in a `<script>` tag.
1040
1598
  */
1041
1599
  content_body?: string;
1042
1600
  /**
1043
- * @description URI of the resource. Required when `content_type` is `external_ref`; optional for `markdown` content.
1601
+ * @description URI of the resource. Required when `content_type` is `external_ref`, `pdf`, `image`, `video`, or remote `html`.
1044
1602
  * @example https://vimeo.com/123456789
1045
1603
  */
1046
1604
  content_uri?: string;
@@ -1051,7 +1609,7 @@ declare interface components {
1051
1609
  external_identifier?: string;
1052
1610
  /**
1053
1611
  * Format: byte
1054
- * @description (Optional) Article teaser, written in markdown and base64 encoded.
1612
+ * @description (Optional) Article teaser, written in markdown and base64 encoded. Must be base64-encoded before sending (e.g. `btoa(teaserText)`).
1055
1613
  */
1056
1614
  teaser?: string;
1057
1615
  /** @description Price for the content in cents. */
@@ -1071,23 +1629,23 @@ declare interface components {
1071
1629
  [key: string]: unknown;
1072
1630
  };
1073
1631
  };
1074
- /** @description Response shape for a single content item. The presence of `content_body` vs `content_uri` depends on `content_type`: `markdown` includes `content_body`; `external_ref` includes `content_uri` (the external URI) and optionally `external_identifier`. */
1632
+ /** @description Response shape for a single content item. The presence of `content_body` vs `content_uri` depends on `content_type` and storage mode: `markdown`, `brokered`, and inline `html` include `content_body`; `external_ref`, `pdf`, `image`, `video`, and remote `html` include `content_uri`. */
1075
1633
  ContentResponse: {
1076
1634
  id: string;
1077
1635
  /**
1078
- * @description The type of content.
1636
+ * @description The type of content. `pdf`, `image`, and `video` are remote-only types; `content_uri` is withheld until purchase. `brokered` is system-managed (e.g. Tollbit-sourced) and cannot be created directly via this API.
1079
1637
  * @enum {string}
1080
1638
  */
1081
- content_type: 'markdown' | 'external_ref';
1639
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1082
1640
  /** @description Content title */
1083
1641
  title: string;
1084
1642
  /**
1085
1643
  * Format: byte
1086
- * @description Full article body in markdown, base64 encoded. Present when `content_type` is `markdown`.
1644
+ * @description Full article body, base64 encoded. Present when `content_type` is `markdown` or inline `html`. Must be base64-decoded before rendering (e.g. `atob(content.content_body ?? '')`). Note: HTML special characters (`<`, `>`, `&`) must be unicode-escaped when embedding the JSON in a `<script>` tag.
1087
1645
  */
1088
1646
  content_body?: string | null;
1089
1647
  /**
1090
- * @description URI of the external resource. Present when `content_type` is `external_ref`.
1648
+ * @description URI of the external resource. Present when `content_type` is `external_ref`, `pdf`, `image`, `video`, or remote `html`.
1091
1649
  * @example https://vimeo.com/123456789
1092
1650
  */
1093
1651
  content_uri?: string | null;
@@ -1096,9 +1654,11 @@ declare interface components {
1096
1654
  * @example vimeo:123456789
1097
1655
  */
1098
1656
  external_identifier?: string | null;
1657
+ /** @description Canonical URL of this content on its origin site. Used by the x402 verify-origin endpoint to bind a third-party page to a Ledewire content record. */
1658
+ resource_url?: string | null;
1099
1659
  /**
1100
1660
  * Format: byte
1101
- * @description Article teaser, base64 encoded.
1661
+ * @description Article teaser, base64 encoded. Must be base64-decoded before rendering (e.g. `atob(content.teaser ?? '')`).
1102
1662
  */
1103
1663
  teaser: string;
1104
1664
  /** @description Price for the content in cents. */
@@ -1189,12 +1749,12 @@ declare interface components {
1189
1749
  ContentListItem: {
1190
1750
  id: string;
1191
1751
  /** @enum {string} */
1192
- content_type: 'markdown' | 'external_ref';
1752
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1193
1753
  title: string;
1194
1754
  price_cents: number;
1195
1755
  /**
1196
1756
  * Format: byte
1197
- * @description Article teaser, base64 encoded. Null when not set.
1757
+ * @description Article teaser, base64 encoded. Null when not set. Must be base64-decoded before rendering (e.g. `atob(item.teaser ?? '')`).
1198
1758
  */
1199
1759
  teaser: string | null;
1200
1760
  /** @enum {string} */
@@ -1205,11 +1765,13 @@ declare interface components {
1205
1765
  content_uri: string | null;
1206
1766
  /** @description Namespaced platform ID for `external_ref` content. Null for other types. */
1207
1767
  external_identifier?: string | null;
1768
+ /** @description Canonical URL of this content on its origin site. Used by the x402 verify-origin endpoint. */
1769
+ resource_url?: string | null;
1208
1770
  };
1209
1771
  /**
1210
1772
  * @description Full content detail plus real-time access and wallet context for a specific user. Returned by the buyer-facing `GET /v1/content/:id/with-access` endpoint.
1211
1773
  *
1212
- * **URI gating for `external_ref` content:** `content_uri` (the external URI) is only present in the response when `access_info.has_purchased` is `true`. For all other states (unauthenticated, insufficient funds, not yet purchased) it is omitted, ensuring buyers cannot access the Vimeo link, PDF URI, or other external resource without completing a purchase.
1774
+ * **URI gating for remote content:** `content_uri` is only present in the response when `access_info.has_purchased` is `true`. This applies to `external_ref` content and remote-mode `html` content (where the payload is a URI rather than an inline body). For all other states (unauthenticated, insufficient funds, not yet purchased) it is omitted, ensuring buyers cannot access the Vimeo link, PDF URI, or other external resource without completing a purchase.
1213
1775
  */
1214
1776
  ContentWithAccessResponse: components['schemas']['ContentResponse'] & {
1215
1777
  access_info: components['schemas']['ContentAccessInfo'];
@@ -1245,6 +1807,17 @@ declare interface components {
1245
1807
  /** Format: date-time */
1246
1808
  timestamp: string;
1247
1809
  status: components['schemas']['PurchaseStatus'];
1810
+ /**
1811
+ * @description The delivered work, present **only** on the response to `POST /v1/purchases` and only when the deliverable is something we hold. It is the delivery, not a field of the purchase record: the same purchase read back from `GET /v1/purchases/{id}` or listed from `GET /v1/purchases` does not carry it, and there is no route that serves it again.
1812
+ *
1813
+ * That asymmetry is the rule rather than an omission. A Buyer keeps what they were given — single use is non-transferable, not read-once — but LedeWire does not hand the same bytes over a second time without a second grant, and every read from LedeWire is a purchase.
1814
+ */
1815
+ content_body?: string;
1816
+ /**
1817
+ * Format: uri
1818
+ * @description Where the work lives, in place of `content_body`, for a deliverable hosted elsewhere. Same rule: present only on the response to the purchase that paid for it.
1819
+ */
1820
+ content_uri?: string;
1248
1821
  };
1249
1822
  MerchantSaleResponse: {
1250
1823
  id: string;
@@ -1301,7 +1874,7 @@ declare interface components {
1301
1874
  has_sufficient_funds?: boolean | null;
1302
1875
  has_purchased: boolean;
1303
1876
  /** @enum {string} */
1304
- next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
1877
+ next_required_action: 'authenticate' | 'fund_wallet' | 'purchase';
1305
1878
  };
1306
1879
  };
1307
1880
  WalletPaymentStatusResponse: {
@@ -1329,6 +1902,491 @@ declare interface components {
1329
1902
  };
1330
1903
  };
1331
1904
  };
1905
+ /** @description A single x402 v2 resource entry returned by the Bazaar discovery endpoint. */
1906
+ X402BazaarResource: {
1907
+ /** @description Canonical URL for this content. Equal to `resource_url` when the content has one; otherwise constructed as `{base_url}/v1/x402/contents/{id}`. */
1908
+ resource: string;
1909
+ /** @enum {string} */
1910
+ type: 'http';
1911
+ /** @enum {integer} */
1912
+ x402Version: 2;
1913
+ accepts: {
1914
+ /** @example ledewire-wallet */
1915
+ scheme: string;
1916
+ /** @example ledewire:v1 */
1917
+ network: string;
1918
+ }[];
1919
+ /** @description Unix timestamp (seconds) of the content's last update. */
1920
+ lastUpdated: number;
1921
+ metadata: {
1922
+ title: string;
1923
+ /** @description Plain UTF-8 preview text. Never base64. */
1924
+ teaser: string;
1925
+ store_name: string;
1926
+ /** @description `metadata["category"]` when present; otherwise falls back to `content_type`. Never null. */
1927
+ category: string;
1928
+ };
1929
+ };
1930
+ /** @description Response from the x402 Bazaar discovery endpoint. */
1931
+ X402BazaarDiscoveryResponse: {
1932
+ /** @description Total number of public resources (ignores pagination). */
1933
+ total: number;
1934
+ resources: components['schemas']['X402BazaarResource'][];
1935
+ };
1936
+ /** @description Response body returned by the x402 content endpoint on a successful `200`. Delivers the purchased content directly in the settlement response. `content_body` is present when `content_type` is `markdown`, `brokered`, or inline `html`; `content_uri` is present when `content_type` is `external_ref`, `pdf`, `image`, `video`, or remote `html`. `purchase_id` is the UUID of the settled Purchase record (null for free content). */
1937
+ X402ContentResponse: {
1938
+ id: string;
1939
+ /** @enum {string} */
1940
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1941
+ title: string;
1942
+ price_cents: number;
1943
+ /** @description Plain UTF-8 text preview. */
1944
+ teaser: string;
1945
+ /** @enum {string} */
1946
+ visibility: 'public' | 'unlisted' | 'private';
1947
+ metadata?: {
1948
+ [key: string]: unknown;
1949
+ };
1950
+ external_identifier?: string | null;
1951
+ /** @description UUID of the settled Purchase record. Null for free content. */
1952
+ purchase_id?: string | null;
1953
+ /** @description Canonical URL of this content on its origin site. Set when content was registered via a pricing rule or manual resource_url assignment. */
1954
+ resource_url?: string | null;
1955
+ /** @description Full article body in plain UTF-8 markdown. Present when content_type is markdown. */
1956
+ content_body?: string | null;
1957
+ /** @description URI of the external resource. Present when content_type is `external_ref`, `pdf`, `image`, or `video`. */
1958
+ content_uri?: string | null;
1959
+ };
1960
+ /** @description Returned when the URL matches a registered Ledewire content item. */
1961
+ VerifyOriginVerified: {
1962
+ /** @enum {boolean} */
1963
+ verified: true;
1964
+ /** @description UUID of the matching Content record. */
1965
+ content_id: string;
1966
+ /** @description UUID of the store that owns this content. */
1967
+ store_id: string;
1968
+ /** @description Price in cents as a string (e.g. "10" = $0.10). */
1969
+ amount: string;
1970
+ /** @description Content title. */
1971
+ title: string;
1972
+ };
1973
+ /** @description Returned when the URL is not registered or the URL is invalid. */
1974
+ VerifyOriginUnverified: {
1975
+ /** @enum {boolean} */
1976
+ verified: false;
1977
+ };
1978
+ /** @description JSON Web Key Set (RFC 7517) containing the RS256 public key for x402 accessToken verification. */
1979
+ JwksResponse: {
1980
+ keys: {
1981
+ /** @enum {string} */
1982
+ kty: 'RSA';
1983
+ /** @enum {string} */
1984
+ use: 'sig';
1985
+ /** @enum {string} */
1986
+ alg: 'RS256';
1987
+ /** @description Key ID. Matches the `kid` header claim in minted access tokens. */
1988
+ kid: string;
1989
+ /** @description RSA modulus (Base64url-encoded). */
1990
+ n: string;
1991
+ /** @description RSA public exponent (Base64url-encoded). */
1992
+ e: string;
1993
+ }[];
1994
+ };
1995
+ /**
1996
+ * @description One work in a Selection, and what happened to it. Every submitted row appears, including the ones we refused — a malformed line and a duplicate spelling are dispositions rather than omissions, and a row that vanished silently between upload and quote is the failure the acknowledgement step exists to prevent.
1997
+ *
1998
+ * `line_state`, `purchased` and `delivery_state` answer three different questions and none implies another. A work can be quoted and never reached, purchased and not delivered, or excluded and therefore never attempted.
1999
+ */
2000
+ AcquisitionWork: {
2001
+ /** @description 1-based place in the submitted file, so a Buyer can find the row they wrote. */
2002
+ position: number;
2003
+ /** @description The address exactly as submitted. */
2004
+ submitted_url: string;
2005
+ /** @description The address after canonicalization — the identity function for a brokered work. Two rows that differ only in `www.`, a query string or a fragment name one work, and the second is excluded as a duplicate rather than bought twice. */
2006
+ canonical_url?: string | null;
2007
+ /**
2008
+ * @description What the quote said. `firm` was priced from a rate the broker answered with; `estimated` from a rate we already held because the broker could not be asked; `excluded` will not be bought.
2009
+ * @enum {string}
2010
+ */
2011
+ line_state: 'pending' | 'firm' | 'estimated' | 'excluded';
2012
+ /**
2013
+ * @description Present only on an excluded line. `excluded_rate_unavailable` is the one transient reason.
2014
+ * @enum {string|null}
2015
+ */
2016
+ exclusion_reason?: 'excluded_malformed' | 'excluded_duplicate' | 'excluded_no_rate' | 'excluded_free_not_supported' | 'excluded_not_deliverable' | 'excluded_insufficient_rights' | 'excluded_above_price_ceiling' | 'excluded_rate_unavailable' | null;
2017
+ /** @description What this work was quoted at, in micros. **Never published in cents.** Per-work cents are display-only and non-summable for a Bulk acquisition — the authoritative figure is micros and the acquisition total is what reconciles, so a client is given nothing here that invites adding up to the charge. */
2018
+ price_micros?: number | null;
2019
+ /**
2020
+ * @description Whether a Purchase was made for this work.
2021
+ *
2022
+ * **History, not access.** Under single use a completed Purchase is a receipt for a delivery that has already happened; it grants nothing further, and is never a reason to serve a body again. Reported so a Buyer can tell "we bought this and the bytes did not arrive" — a delivery we owe — from "we never bought it".
2023
+ */
2024
+ purchased: boolean;
2025
+ /**
2026
+ * @description What the run did. `pending` means not yet attempted — which is also what an excluded work reads, because it was never attempted and `undelivered` would claim we tried and failed. Those are different answers to a Buyer and only the second is worth a retry.
2027
+ * @enum {string}
2028
+ */
2029
+ delivery_state: 'pending' | 'delivered' | 'undelivered';
2030
+ /**
2031
+ * @description Why an attempted work did not arrive, as a type rather than prose. Only `retrieval_failed` is transient; the rest fail identically on every attempt, so retrying one spends a run's budget on works that need re-pricing, a fix, or money.
2032
+ * @enum {string|null}
2033
+ */
2034
+ failure_reason?: 'retrieval_failed' | 'not_licensable' | 'price_drifted' | 'client_error' | 'insufficient_funds' | 'cap_refused' | null;
2035
+ /** @description How many times retrieval was tried, the successful attempt included. */
2036
+ attempts?: number | null;
2037
+ };
2038
+ /**
2039
+ * @description A Publication a Buyer can license from — the title a Bulk acquisition is organised around. Listed from our own registry, which is reconciled daily against the Broker's directory, rather than from a live call.
2040
+ *
2041
+ * Licensability is a rights answer, not a quality one. A publication that publishes no `FULL_USE` rate is still listed, with `bulk_licensable: false`: its works can be bought one at a time but are excluded from a corpus as `excluded_insufficient_rights`.
2042
+ */
2043
+ Publication: {
2044
+ /** Format: uuid */
2045
+ id: string;
2046
+ /** @description The Broker's display name for the publication. */
2047
+ name: string;
2048
+ /** @description The domains the publication is listed under, spelled as the Broker spells them — including a `www.` prefix where it has one. */
2049
+ domains: string[];
2050
+ /** @description Whether works from this publication can come back in a Bulk acquisition's corpus — true when it publishes a `FULL_USE` rate. */
2051
+ bulk_licensable: boolean;
2052
+ };
2053
+ /** @description Every Publication the Broker reports as ready to license, paginated. */
2054
+ PublicationListResponse: {
2055
+ data: components['schemas']['Publication'][];
2056
+ pagination: components['schemas']['PaginationMeta'];
2057
+ };
2058
+ /** @description The MCP `list_publications` result: one page of the Publications a Buyer can license from, paged by offset and limit as `search_content` is. */
2059
+ McpListPublicationsResult: {
2060
+ publications: components['schemas']['Publication'][];
2061
+ total_count: number;
2062
+ offset: number;
2063
+ limit: number;
2064
+ };
2065
+ /** @description A work a Publication has available to license, as the Broker's catalog lists it. `url` is exactly what a Selection takes. */
2066
+ PublicationWork: {
2067
+ /** Format: uri */
2068
+ url: string;
2069
+ /**
2070
+ * Format: date-time
2071
+ * @description When the Broker says the work was last modified. Null where it supplies none, which on some publications is every work.
2072
+ */
2073
+ last_mod: string | null;
2074
+ };
2075
+ /** @description One page of a Publication's works, read live from the Broker's catalog. Unpriced: pricing happens when a Selection of these URLs is quoted. */
2076
+ PublicationWorkListResponse: {
2077
+ /** Format: uuid */
2078
+ publication_id: string;
2079
+ /** @description The Publication domain this page was read from, in the Broker's spelling. A publication with several domains is walked one domain after another. Null only when the publication has no live domain to read. */
2080
+ domain: string | null;
2081
+ /**
2082
+ * @description How `from`/`to` applied to this page's domain. `applied` means the Broker filtered and every work is in range. `unsupported` means the filtered query returned nothing while the same domain unfiltered does not: the domain's catalog carries no modification dates, so the range cannot be answered. That is not the same as having no works in the range. The unfiltered works are deliberately not returned in their place, because they are not known to fall inside the range.
2083
+ * @enum {string}
2084
+ */
2085
+ date_filter: 'not_requested' | 'applied' | 'unsupported';
2086
+ works: components['schemas']['PublicationWork'][];
2087
+ /** @description Opaque. Pass it back as `cursor` with the same `from`/`to` for the next page. Null means the walk is over; an empty page with a cursor is not the end. */
2088
+ next_cursor: string | null;
2089
+ };
2090
+ /** @description The MCP `list_publication_works` result. It is the same page as `GET /v1/publications/{id}/works` returns, by reference, so the two cannot drift. */
2091
+ McpListPublicationWorksResult: components['schemas']['PublicationWorkListResponse'];
2092
+ /** @description Per-work dispositions for a Bulk acquisition, paginated. Its own endpoint rather than a field on the acquisition because a Selection may name 10,000 works and the acquisition resource has to stay cheap enough to poll for hours. */
2093
+ PaginatedAcquisitionWorkList: {
2094
+ data: components['schemas']['AcquisitionWork'][];
2095
+ pagination: components['schemas']['PaginationMeta'];
2096
+ };
2097
+ /**
2098
+ * @description A Bulk acquisition — the one resource the whole flow hangs off. The Buyer holds this id from the moment they submit a Selection, and every later step reads or advances it: the quote arrives on it, the acknowledgement is a timestamp on it, the hold is sized from its total, and the run writes its outcome back to it.
2099
+ *
2100
+ * `status` and `quote_state` are two independent state machines and are not collapsible. `status` is the money — `quoted` holds nothing, `authorized` and `acquiring` hold funds, and every terminal state releases what is left. `quote_state` is whether the prices are in. A `quoted` acquisition whose quote is still `pending` has a snapshot and no prices, and a client that read one field would mis-read exactly that case.
2101
+ */
2102
+ AcquisitionResponse: {
2103
+ /** Format: uuid */
2104
+ id: string;
2105
+ /**
2106
+ * @description Where the money is. `quoted` reserves nothing; `authorized` and `acquiring` hold the stated maximum chargeable total; `settled`, `cancelled` and `failed` are terminal and have released whatever was not captured.
2107
+ * @enum {string}
2108
+ */
2109
+ status: 'quoted' | 'authorized' | 'acquiring' | 'settled' | 'cancelled' | 'failed';
2110
+ /**
2111
+ * @description Whether the Selection has been priced. Pricing is asynchronous — resolving rates for 10,000 works is ~200 upstream batch calls under undocumented limits — so a submitted Selection comes back `pending` and the client polls this.
2112
+ * @enum {string}
2113
+ */
2114
+ quote_state: 'pending' | 'ready' | 'failed';
2115
+ /** @description How many distinct publications the Selection spans. Nothing in a Bulk acquisition is keyed on a single publication. */
2116
+ publication_count: number;
2117
+ /** @description How many rows the upload named and we accepted. Published alongside `work_count` because until pricing finishes the two differ and only this one is known: nothing carries a price while `quote_state` is `pending`, so a freshly submitted 10,000-row Selection reports `work_count: 0` and would otherwise read exactly like an empty one. */
2118
+ submitted_work_count: number;
2119
+ /** @description Works that carry a price — firm or estimated. Excluded rows are not counted here, and neither is anything still waiting to be priced. */
2120
+ work_count: number;
2121
+ quote: components['schemas']['AcquisitionQuote'];
2122
+ /** @description What we cannot sell, grouped by reason and counted. Grouped rather than enumerated because a 10,000-work Selection can exclude thousands of rows and the acknowledgement step asks the Buyer to register what kinds of thing are not coming; the per-work detail is `GET /v1/acquisitions/{id}/works`, which paginates. */
2123
+ exclusions: {
2124
+ /**
2125
+ * @description Six of these are the Buyer's to act on and one is ours. `excluded_rate_unavailable` is the only transient one — it says we could not reach the broker to ask, and is the only reason here worth asking us to retry. `excluded_not_deliverable` is a broker failure we observed; `excluded_insufficient_rights` is a rights decision we made; `excluded_above_price_ceiling` is a price we declined. Those three are the ones most easily collapsed and must not be.
2126
+ * @enum {string}
2127
+ */
2128
+ reason: 'excluded_malformed' | 'excluded_duplicate' | 'excluded_no_rate' | 'excluded_free_not_supported' | 'excluded_not_deliverable' | 'excluded_insufficient_rights' | 'excluded_above_price_ceiling' | 'excluded_rate_unavailable';
2129
+ count: number;
2130
+ }[];
2131
+ /** @description What the run has done. `purchased` and `delivered` are separate facts and both are reported, because a Purchase can complete and its body fail to arrive — a delivery LedeWire owes, and not a reason to charge again. An acquisition with 10,000 purchases and 9,700 deliveries has 300 outstanding, which no single number says. */
2132
+ delivery: {
2133
+ /** @description Works a Purchase was made for. History, not an entitlement to re-read. */
2134
+ purchased: number;
2135
+ delivered: number;
2136
+ /** @description Attempted and not delivered. An excluded work is not counted here — it was never attempted, and "not in your corpus because we refused to quote it" is a different answer from "not in your corpus because retrieval failed". */
2137
+ undelivered: number;
2138
+ /** @description Quoted and not yet reached. What a resumed run will attempt. */
2139
+ outstanding: number;
2140
+ };
2141
+ /** Format: date-time */
2142
+ created_at: string;
2143
+ };
2144
+ /** @description The priced Selection, and the promise made about it. */
2145
+ AcquisitionQuote: {
2146
+ /** @enum {string} */
2147
+ state: 'pending' | 'ready' | 'failed';
2148
+ /** @description Subtotal of works priced from a rate the broker answered with, in micros (millionths of a dollar). */
2149
+ firm_micros: number;
2150
+ /** @description Subtotal of works priced from a rate we already held, because the broker could not be asked. Estimated, never firm — the price may have moved, and observed broker prices move materially in both directions. What protects the Buyer is `maximum_chargeable_total_cents`. */
2151
+ estimated_micros: number;
2152
+ /**
2153
+ * @description The most this acquisition can charge. Settlement clamps captures to it and absorbs any drift above it from margin, so it is a ceiling rather than an estimate — an acquisition that cannot deliver every work charges less.
2154
+ *
2155
+ * Subtotals are published in micros and the ceiling in cents deliberately: rounding happens **once**, on the total. Two rounded cent subtotals would not reliably sum to the rounded total, and publishing them that way would invite a client to add them up and disagree with the figure we charge. Per-work prices never sum to this either.
2156
+ */
2157
+ maximum_chargeable_total_cents: number;
2158
+ /** Format: date-time */
2159
+ quoted_at?: string | null;
2160
+ /**
2161
+ * Format: date-time
2162
+ * @description When the stated maximum chargeable total stops standing — 24 hours after pricing. Published rather than left to the client to compute, so the window is not hard-coded against a constant of ours. Re-pricing is cheap: it re-prices the snapshot without re-resolving it, so the Buyer gets the same works at today's prices without uploading anything again.
2163
+ */
2164
+ expires_at?: string | null;
2165
+ /**
2166
+ * Format: date-time
2167
+ * @description When the Buyer registered what we cannot sell them. Withdrawn on every re-quote, so it always refers to the current set of exclusions and never to a previous one — an acknowledgement carried forward would be a Buyer authorizing a list they never saw.
2168
+ */
2169
+ exclusions_acknowledged_at?: string | null;
2170
+ };
2171
+ /**
2172
+ * @description Where a Bulk acquisition's Corpus is — a **state**, never an error.
2173
+ *
2174
+ * A corpus is a rendering of Purchases the Buyer already holds, not an entitlement of its own, which is what makes everything about it cheap: it can be discarded and rebuilt, and rebuilding grants nothing that was not already granted. So a blob past its 30-day retention answers `rebuild_required` at 200 rather than 404 or 410 — those would tell the Buyer something was lost, and nothing was.
2175
+ *
2176
+ * `rebuild_required` covers "never assembled" as well as "expired", on purpose: from the Buyer's side they are one situation — there is no file, ask for one — and splitting them would put our bookkeeping into their contract.
2177
+ */
2178
+ CorpusResponse: {
2179
+ /**
2180
+ * @description `ready` is downloadable now. `assembling` means a run is in flight and `pending` that one is queued — poll either. `rebuild_required` means ask for it again, with `POST /v1/acquisitions/{id}/corpus`. `failed` carries a reason.
2181
+ * @enum {string}
2182
+ */
2183
+ state: 'pending' | 'assembling' | 'ready' | 'rebuild_required' | 'failed';
2184
+ /**
2185
+ * @description The archive rendering. There is no size-conditional switching — a client never branches on how big its corpus is.
2186
+ * @enum {string|null}
2187
+ */
2188
+ format?: 'tar_gz' | 'jsonl_gz' | null;
2189
+ /** @description Size of the assembled archive. Null unless `ready`. */
2190
+ byte_size?: number | null;
2191
+ /**
2192
+ * Format: date-time
2193
+ * @description When the blob is retired, 30 days after assembly. The signed Manifest has no expiry and is served separately from `/v1/acquisitions/{id}/manifest`, because an auditor years later must be able to obtain the record without the corpus.
2194
+ */
2195
+ expires_at?: string | null;
2196
+ /** @description Why assembly broke. Present only when `state` is `failed`. */
2197
+ failure_reason?: string | null;
2198
+ /**
2199
+ * @description Where to fetch the archive, relative to this API, and null in every state but `ready`.
2200
+ *
2201
+ * An authenticated route of ours rather than a presigned store URL. Fetch it with the same bearer token as this request and the response is the archive itself — there is no redirect to follow and no second URL to capture. A link to the object store handed out here would stand whether or not its reader could authenticate, and a client may log, cache or forward a poll response. The corpus is the most sensitive thing this API serves, so the authorization decision belongs on every request for it.
2202
+ */
2203
+ download_url?: string | null;
2204
+ };
2205
+ /**
2206
+ * @description The signed Manifest of a Bulk acquisition — the audit record itself, not a summary of one. There is no separate receipt: two representations of the same facts eventually disagree, and a manifest that disagrees with the signed record is worse than no manifest.
2207
+ *
2208
+ * Served verbatim from the bytes stored at first assembly. It is **not** re-derived from the rows it was generated from, because the signature covers bytes rather than meaning — a re-derived document would read the same, hash differently, and fail its own signature. A row edited in 2029 therefore cannot change a document written in 2026.
2209
+ *
2210
+ * Permanent. The corpus blob expires 30 days after assembly; this does not, because an auditor years later must be able to obtain the record without the corpus.
2211
+ */
2212
+ CorpusManifestResponse: {
2213
+ /** @description The manifest document. Every work in the Selection appears with a disposition — `delivered`, `undelivered`, or the name of its exclusion reason — because silent omission is a defect. A delivered entry additionally carries a content hash computed at capture, the licence asked for and the licence granted with its permission set, the captured licence document's path and hash, and what the Buyer was charged in micros. Cost columns are never in here. */
2214
+ manifest: {
2215
+ [key: string]: unknown;
2216
+ };
2217
+ /** @description A detached JWS (RFC 7515 with RFC 7797 `b64:false`) over the manifest's exact canonical bytes — a protected header, two dots and a signature. Detached so the manifest stays a plain readable file that a compliance tool can ingest without a crypto library. */
2218
+ signature: string;
2219
+ /** @description SHA-256 of the canonical manifest bytes, hex-encoded. */
2220
+ digest: string;
2221
+ /** @description Which key history entry to resolve. An index, never a credential — it is a 96-bit truncation, so a verifier matches on all 32 raw public-key bytes. */
2222
+ signing_kid: string;
2223
+ /** @description The head of the key history at signing time. A verifier checks that this digest appears somewhere in the chain being served — not that it equals the present head, since the history grows. What it catches is truncation or a fork since this manifest was issued, which needs no key at all, only control of the document. */
2224
+ key_history_head_digest: string;
2225
+ /**
2226
+ * Format: date-time
2227
+ * @description When the manifest was signed. Our own assertion until #989's RFC 3161 token lands, and the compromise rule is only as trustworthy as this field.
2228
+ */
2229
+ signed_at: string;
2230
+ /** @description Where a verifier resolves `signing_kid`. */
2231
+ key_history_url?: string;
2232
+ };
2233
+ /**
2234
+ * @description The append-only, hash-chained log of every Ed25519 key that has signed an audit-export manifest. This is the trust anchor a verifier resolves a manifest's `kid` through.
2235
+ * It is a log of *events*, not a table of keys: there is no `valid_until` or `retired_reason` field. A key's window closes at the `valid_from` of the next `activate` entry, and it is compromised if a `revoke` entry names it. Those are derived by the reader, because an entry that could be edited after a later one chained to it would break the chain. See docs/audit-export-key-history.md.
2236
+ */
2237
+ SigningKeyHistoryResponse: {
2238
+ /** @description Bumped when the document's shape changes in a way a reader must notice. */
2239
+ schema_version: number;
2240
+ /** @description Every entry, in chain order, starting at sequence 0. */
2241
+ entries: {
2242
+ /**
2243
+ * @description `activate` brings a key into service and carries `public_key` and `valid_from`. `revoke` records that the key named by `kid` was stolen and carries `compromised_at`.
2244
+ * @enum {string}
2245
+ */
2246
+ kind: 'activate' | 'revoke';
2247
+ /** @description base64url(SHA-256(raw public key))[0, 16]. An index into this document, never a credential — it is a 96-bit truncation, so a verifier matches on all 32 raw public-key bytes instead. */
2248
+ kid: string;
2249
+ /** @description Monotonic and gapless, starting at 0. */
2250
+ sequence: number;
2251
+ /** @description The raw 32-byte Ed25519 public key, base64url-encoded without padding. Present on `activate` entries only. */
2252
+ public_key?: string;
2253
+ /**
2254
+ * Format: date-time
2255
+ * @description When this key entered service. Present on `activate` entries only.
2256
+ */
2257
+ valid_from?: string;
2258
+ /**
2259
+ * Format: date-time
2260
+ * @description When the key named by `kid` is believed to have been stolen — ordinarily earlier than when it was discovered. Signatures dated before it stand; those at or after it are void. Present on `revoke` entries only.
2261
+ */
2262
+ compromised_at?: string;
2263
+ /** @description SHA-256 (hex) of the previous entry's RFC 8785 canonical JSON, including its signature. Absent on the root entry alone. */
2264
+ previous_entry_digest?: string;
2265
+ /** @description base64url Ed25519 signature over this entry's canonical JSON excluding this field, made by the key in service before it — which, for a `revoke`, is not the key it names. Absent on the root entry alone. */
2266
+ signature?: string;
2267
+ }[];
2268
+ };
2269
+ /** @description x402 v2 settlement result. Base64-encoded JSON returned in the `PAYMENT-RESPONSE` header on a successful `200`. `accessToken` is present when an RS256 signing key is configured; omitted in environments without credentials. */
2270
+ SettlementResponse: {
2271
+ /** @enum {boolean} */
2272
+ success: true;
2273
+ /** @description UUID of the settled Purchase record. */
2274
+ transaction: string;
2275
+ /** @enum {string} */
2276
+ network: 'ledewire:v1';
2277
+ /** @description UUID of the buying user. */
2278
+ payer: string;
2279
+ /** @description Short-lived RS256-signed JWT for offline entitlement verification. Verifiable via `/.well-known/x402-jwks.json`. Omitted if signing key is not configured. */
2280
+ accessToken?: string | null;
2281
+ };
2282
+ MerchantPricingRule: {
2283
+ /** @description UUID of the pricing rule. */
2284
+ id: string;
2285
+ /** @description UUID of the owning store. */
2286
+ store_id: string;
2287
+ /** @description Glob URL pattern (supports * and ** wildcards). Must start with http:// or https://. */
2288
+ url_pattern: string;
2289
+ /** @description Price in cents applied to content matching this pattern. */
2290
+ price_cents: number;
2291
+ /** @description Whether the rule is currently active. Inactive rules are ignored during URL matching. */
2292
+ active: boolean;
2293
+ /** Format: date-time */
2294
+ created_at: string;
2295
+ /** Format: date-time */
2296
+ updated_at: string;
2297
+ };
2298
+ MerchantDomainVerification: {
2299
+ /** @description UUID of the domain verification record. */
2300
+ id: string;
2301
+ /** @description UUID of the owning store. */
2302
+ store_id: string;
2303
+ /** @description The verified domain (www. prefix is stripped on creation). */
2304
+ domain: string;
2305
+ /**
2306
+ * @description Verification status updated by DomainVerifyJob.
2307
+ * @enum {string}
2308
+ */
2309
+ status: 'pending' | 'verified' | 'failed';
2310
+ /** @description DNS TXT record name the merchant must add (e.g. _ledewire-verify.example.com). */
2311
+ txt_record_name: string;
2312
+ /** @description DNS TXT record value the merchant must set. */
2313
+ txt_record_value: string;
2314
+ /**
2315
+ * Format: date-time
2316
+ * @description Timestamp of successful verification. Null until verified.
2317
+ */
2318
+ verified_at?: string | null;
2319
+ /**
2320
+ * Format: date-time
2321
+ * @description Timestamp of the most recent verification check (success or failure).
2322
+ */
2323
+ checked_at?: string | null;
2324
+ /** Format: date-time */
2325
+ created_at: string;
2326
+ };
2327
+ /** @description RFC 8414 Authorization Server Metadata. MCP clients fetch this to discover the authorization flow. `registration_endpoint` is what tells a client that Dynamic Client Registration is available at all — without it, clients that do not speak CIMD fall back to asking a human for a client id. */
2328
+ OauthAuthorizationServerMetadata: {
2329
+ issuer: string;
2330
+ authorization_endpoint: string;
2331
+ token_endpoint: string;
2332
+ revocation_endpoint?: string;
2333
+ /** @description RFC 7591 Dynamic Client Registration endpoint. */
2334
+ registration_endpoint: string;
2335
+ scopes_supported: string[];
2336
+ response_types_supported: string[];
2337
+ code_challenge_methods_supported: string[];
2338
+ client_id_metadata_document_supported?: boolean;
2339
+ token_endpoint_auth_methods_supported?: string[];
2340
+ };
2341
+ /** @description RFC 7591 client metadata. Only `redirect_uris` is required; unrecognised members are ignored rather than rejected, so clients sending extensions this server does not implement still register successfully. */
2342
+ OauthClientRegistrationRequest: {
2343
+ /** @description Must be https, or http on a loopback host (`localhost`, `127.0.0.1`, `[::1]`). Fragments are not permitted. These become the allowlist for the issued client_id. */
2344
+ redirect_uris: string[];
2345
+ client_name?: string;
2346
+ client_uri?: string;
2347
+ logo_uri?: string;
2348
+ tos_uri?: string;
2349
+ policy_uri?: string;
2350
+ contacts?: string[];
2351
+ software_id?: string;
2352
+ software_version?: string;
2353
+ /** @description Space-delimited. Scopes this server does not support are filtered out, and the response omits `scope` entirely when none was requested — registration decides nothing about what a token later grants. */
2354
+ scope?: string;
2355
+ grant_types?: ('authorization_code' | 'refresh_token')[];
2356
+ response_types?: 'code'[];
2357
+ /**
2358
+ * @description Optional, and may only be `none`. Registered clients are public — no client_secret is issued, and PKCE protects the code exchange.
2359
+ * @enum {string}
2360
+ */
2361
+ token_endpoint_auth_method?: 'none';
2362
+ };
2363
+ /** @description RFC 7591 §3.2.1 client information response. Carries no `client_secret`: a registered client is public and authenticates at the token endpoint with PKCE alone. */
2364
+ OauthClientRegistrationResponse: {
2365
+ /** @description Server-assigned, opaque, `lwc_`-prefixed. Deliberately not a URL, so it is never mistaken for a Client ID Metadata Document. */
2366
+ client_id: string;
2367
+ /** @description Seconds since the Unix epoch. */
2368
+ client_id_issued_at: number;
2369
+ redirect_uris: string[];
2370
+ /** @enum {string} */
2371
+ token_endpoint_auth_method: 'none';
2372
+ grant_types?: string[];
2373
+ response_types?: string[];
2374
+ scope?: string;
2375
+ client_name?: string;
2376
+ client_uri?: string;
2377
+ logo_uri?: string;
2378
+ tos_uri?: string;
2379
+ policy_uri?: string;
2380
+ contacts?: string[];
2381
+ software_id?: string;
2382
+ software_version?: string;
2383
+ };
2384
+ /** @description RFC 7591 §3.2.2 client registration error response. */
2385
+ OauthClientRegistrationErrorResponse: {
2386
+ /** @enum {string} */
2387
+ error: 'invalid_redirect_uri' | 'invalid_client_metadata';
2388
+ error_description?: string;
2389
+ };
1332
2390
  };
1333
2391
  responses: never;
1334
2392
  parameters: never;
@@ -1346,6 +2404,37 @@ export declare type ContentResponse = components['schemas']['ContentResponse'];
1346
2404
  /** A piece of content with buyer access information. */
1347
2405
  export declare type ContentWithAccessResponse = components['schemas']['ContentWithAccessResponse'];
1348
2406
 
2407
+ /**
2408
+ * The REST and x402 web-gate form of a daily-spend-cap refusal, returned with HTTP
2409
+ * `402 Payment Required` by `POST /v1/purchases`, `GET /v1/x402/contents/{id}`, and
2410
+ * acquisition authorization. This is the raw wire shape; the SDK throws it as
2411
+ * {@link SpendCapReachedError} rather than handing back the JSON body directly.
2412
+ */
2413
+ export declare type DailySpendCapReachedErrorBody = components['schemas']['DailySpendCapReachedError'];
2414
+
2415
+ /**
2416
+ * Machine-readable reason on an API error envelope, present on refusals that carry one.
2417
+ * Branch on this rather than on `message`, which is prose and may be reworded.
2418
+ *
2419
+ * - `retrieval_failed` — transient; worth retrying.
2420
+ * - `not_licensable` — report the work as undelivered.
2421
+ * - `price_drifted` — re-quote before retrying.
2422
+ * - `client_error` — ours to fix; must never be retried unchanged.
2423
+ * - `insufficient_funds` — cleared by funding the wallet.
2424
+ * - `daily_spend_cap_reached` — deliberately **not** cleared by funding the wallet; see
2425
+ * {@link SpendCapReachedError}.
2426
+ */
2427
+ export declare type ErrorType = NonNullable<components['schemas']['ErrorResponse']['error']['type']>;
2428
+
2429
+ /**
2430
+ * Known {@link ErrorType} values, widened to accept any other string the API
2431
+ * may introduce. `ErrorType` still drives editor autocomplete; a value the API
2432
+ * emits that isn't (yet) in the spec's enum — e.g. `selection_too_large` on
2433
+ * `POST /v1/acquisitions` — still type-checks and survives on {@link
2434
+ * LedewireError.type} instead of being narrowed away.
2435
+ */
2436
+ declare type ErrorTypeValue = ErrorType | (string & {});
2437
+
1349
2438
  /**
1350
2439
  * Thrown when the authenticated user does not have permission
1351
2440
  * to perform the requested operation.
@@ -1397,7 +2486,8 @@ export declare type ContentWithAccessResponse = components['schemas']['ContentWi
1397
2486
  * ```
1398
2487
  */
1399
2488
  export declare class ForbiddenError extends LedewireError {
1400
- constructor(message: string, code?: number);
2489
+ static readonly brand: string;
2490
+ constructor(message: string, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
1401
2491
  }
1402
2492
 
1403
2493
  /**
@@ -1407,6 +2497,8 @@ export declare class ForbiddenError extends LedewireError {
1407
2497
  * - Injects `Authorization: Bearer <token>` headers automatically
1408
2498
  * - Maps HTTP error responses to typed `LedewireError` subclasses
1409
2499
  * - On receiving a 401, calls `onUnauthorized` once and retries the request
2500
+ * - Per-request `{ auth: false }` (see {@link RequestOptions}) skips all of
2501
+ * the above for public endpoints
1410
2502
  *
1411
2503
  * This is an internal class - consumers should use the
1412
2504
  * package-level client factories (`init` / `createClient`) instead.
@@ -1420,8 +2512,9 @@ declare class HttpClient {
1420
2512
  * GET request with optional query parameters.
1421
2513
  * @param path - API path (e.g. `/v1/wallet/balance`)
1422
2514
  * @param params - Query string parameters. `undefined` values are omitted; numbers are coerced to strings.
2515
+ * @param options - Per-request overrides. Pass `{ auth: false }` for public endpoints.
1423
2516
  */
1424
- get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
2517
+ get<T>(path: string, params?: Record<string, string | number | undefined>, options?: RequestOptions): Promise<T>;
1425
2518
  /**
1426
2519
  * POST request.
1427
2520
  * @param path - API path
@@ -1446,8 +2539,28 @@ declare class HttpClient {
1446
2539
  * @param path - API path
1447
2540
  */
1448
2541
  delete<T = void>(path: string): Promise<T>;
2542
+ /**
2543
+ * GET request that returns the raw, unparsed `Response` instead of decoded JSON.
2544
+ * Used for binary downloads (e.g. `GET /v1/acquisitions/{id}/corpus/download`,
2545
+ * which streams a gzip archive when the corpus is ready, or a JSON
2546
+ * `CorpusResponse` body otherwise).
2547
+ *
2548
+ * Applies the same `Authorization` header injection and single-retry-on-401
2549
+ * behaviour as {@link HttpClient.get}, and maps a non-2xx response to the same
2550
+ * typed errors — it just skips the `response.json()` decode step so the caller
2551
+ * can read the body as a stream, blob, or array buffer.
2552
+ *
2553
+ * @param path - API path (e.g. `/v1/acquisitions/{id}/corpus/download`)
2554
+ * @returns The raw `Response`. Callers are responsible for reading its body.
2555
+ */
2556
+ getRaw(path: string): Promise<Response>;
1449
2557
  private buildUrl;
1450
2558
  private request;
2559
+ /**
2560
+ * Shared auth-injection, single-401-retry, and error-mapping logic behind both
2561
+ * `request()` (JSON in, JSON out) and `getRaw()` (JSON in, raw `Response` out).
2562
+ */
2563
+ private performRequest;
1451
2564
  private throwApiError;
1452
2565
  }
1453
2566
 
@@ -1496,6 +2609,11 @@ export declare function init(config: BrowserClientConfig): BrowserClient;
1496
2609
  * All errors thrown by the SDK are instances of this class,
1497
2610
  * making it safe to use `err instanceof LedewireError` as a type guard.
1498
2611
  *
2612
+ * `instanceof` works even when `err` was constructed by a different bundled
2613
+ * copy of this class — e.g. an error thrown by `@ledewire/x402-client`
2614
+ * checked with the `SpendCapReachedError` bundled into `@ledewire/node`. See
2615
+ * {@link LedewireError[Symbol.hasInstance]}.
2616
+ *
1499
2617
  * @example
1500
2618
  * ```ts
1501
2619
  * try {
@@ -1508,11 +2626,48 @@ export declare function init(config: BrowserClientConfig): BrowserClient;
1508
2626
  * ```
1509
2627
  */
1510
2628
  export declare class LedewireError extends Error {
2629
+ /**
2630
+ * Class-identity brand used for cross-bundle `instanceof` (see {@link
2631
+ * LedewireError[Symbol.hasInstance]}). Hardcoded per class — never derived
2632
+ * from `constructor.name` or `this.name`, either of which a minifier can
2633
+ * rewrite, silently breaking the brand match.
2634
+ */
2635
+ static readonly brand: string;
1511
2636
  /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */
1512
2637
  readonly statusCode: number;
1513
2638
  /** Machine-readable error code from the API error body, if present. */
1514
2639
  readonly code: number | undefined;
1515
- constructor(message: string, statusCode: number, code?: number);
2640
+ /**
2641
+ * Machine-readable reason from the API error body's `error.type`, if present.
2642
+ * See {@link ErrorType} for the documented values and how to branch on them —
2643
+ * the API may emit other values it hasn't documented yet, which still land
2644
+ * here as a plain string rather than being dropped.
2645
+ */
2646
+ readonly type: ErrorTypeValue | undefined;
2647
+ /**
2648
+ * Extra top-level fields from the API error response body, excluding
2649
+ * `error` itself. For example, a `selection_too_large` refusal from `POST
2650
+ * /v1/acquisitions` (422) carries `maximum` and `submitted` alongside
2651
+ * `error`. `undefined` when the body carried no other top-level fields,
2652
+ * which is the common case.
2653
+ */
2654
+ readonly details: Readonly<Record<string, unknown>> | undefined;
2655
+ constructor(message: string, statusCode: number, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
2656
+ /**
2657
+ * Makes `err instanceof LedewireError` (and every subclass below) true even
2658
+ * when `err` was constructed by a *different* bundled copy of this module.
2659
+ * Each published package (`@ledewire/node`, `@ledewire/browser`,
2660
+ * `@ledewire/x402-client`) bundles its own copy of `@ledewire/core` via
2661
+ * tsup, so the classes are distinct objects with unrelated prototypes —
2662
+ * the plain prototype-chain check `instanceof` normally does would fail.
2663
+ *
2664
+ * Falls back to the brand list every instance carries under a
2665
+ * `Symbol.for('@ledewire/error-brands')` key (collected from the `brand`
2666
+ * static along the constructing class's prototype chain): true when the
2667
+ * ordinary check passes, OR the instance's brand list includes this
2668
+ * class's own `brand`.
2669
+ */
2670
+ static [Symbol.hasInstance](instance: unknown): boolean;
1516
2671
  }
1517
2672
 
1518
2673
  /**
@@ -1541,6 +2696,18 @@ export declare class LedewireError extends Error {
1541
2696
  */
1542
2697
  export declare function localStorageAdapter(key?: string): TokenStorage;
1543
2698
 
2699
+ /** An MCP API key record (secret is never included after creation). */
2700
+ export declare type McpApiKey = components['schemas']['McpApiKey'];
2701
+
2702
+ /** Request body for creating a new MCP API key. */
2703
+ export declare type McpApiKeyCreateRequest = components['schemas']['McpApiKeyCreateRequest'];
2704
+
2705
+ /**
2706
+ * Response returned once when an MCP API key is created.
2707
+ * The `secret` is shown exactly once and cannot be retrieved again.
2708
+ */
2709
+ export declare type McpApiKeyCreateResponse = components['schemas']['McpApiKeyCreateResponse'];
2710
+
1544
2711
  /**
1545
2712
  * In-memory token storage (default for both packages).
1546
2713
  * Tokens are cleared when the page unloads or the process exits.
@@ -1560,7 +2727,8 @@ export declare type NextRequiredAction = ContentAccessInfo['next_required_action
1560
2727
  * Thrown when the requested resource does not exist.
1561
2728
  */
1562
2729
  export declare class NotFoundError extends LedewireError {
1563
- constructor(message: string, code?: number);
2730
+ static readonly brand: string;
2731
+ constructor(message: string, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
1564
2732
  }
1565
2733
 
1566
2734
  /**
@@ -1602,12 +2770,26 @@ export declare type PurchaseCreateRequest = components['schemas']['PurchaseCreat
1602
2770
  * failure, such as a price mismatch or a duplicate purchase.
1603
2771
  */
1604
2772
  export declare class PurchaseError extends LedewireError {
1605
- constructor(message: string, statusCode: number, code?: number);
2773
+ static readonly brand: string;
2774
+ constructor(message: string, statusCode: number, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
1606
2775
  }
1607
2776
 
1608
2777
  /** A purchase record. */
1609
2778
  export declare type PurchaseResponse = components['schemas']['PurchaseResponse'];
1610
2779
 
2780
+ /**
2781
+ * Per-request overrides accepted by {@link HttpClient} methods.
2782
+ */
2783
+ declare interface RequestOptions {
2784
+ /**
2785
+ * Whether to attach authentication to this request. Defaults to `true`.
2786
+ * Pass `false` for public endpoints: skips `getAccessToken`, sends no
2787
+ * `Authorization` header, and maps a `401` straight to an {@link AuthError}
2788
+ * without invoking `onUnauthorized`.
2789
+ */
2790
+ auth?: boolean;
2791
+ }
2792
+
1611
2793
  /**
1612
2794
  * Search criteria for seller content search.
1613
2795
  * At least one field must be supplied. All supplied fields must match (AND logic).
@@ -1656,6 +2838,59 @@ export declare interface SellerContentSearchRequest {
1656
2838
  */
1657
2839
  export declare function sessionStorageAdapter(key?: string): TokenStorage;
1658
2840
 
2841
+ /**
2842
+ * Thrown when the buyer's daily spend cap has been reached. Returned as HTTP `402`
2843
+ * by `POST /v1/purchases`, `GET /v1/x402/contents/{id}`, and acquisition
2844
+ * authorization, whenever the API error body's `error.type` is
2845
+ * `'daily_spend_cap_reached'`.
2846
+ *
2847
+ * **Funding the wallet does not clear this.** Unlike a plain `insufficient_funds`
2848
+ * 402 — where adding money to the wallet is enough to retry — a spend cap refusal
2849
+ * is a daily policy limit on the buyer's account (every new buyer starts with a
2850
+ * default cap). It clears only when the spend window rolls over at {@link
2851
+ * SpendCapReachedError.resetsAt}, or when the buyer raises or removes the cap via
2852
+ * `user.spendCap.update()`.
2853
+ *
2854
+ * @example
2855
+ * ```ts
2856
+ * try {
2857
+ * await client.purchases.create({ content_id })
2858
+ * } catch (err) {
2859
+ * if (err instanceof SpendCapReachedError) {
2860
+ * // Do NOT prompt the buyer to fund their wallet — that won't help.
2861
+ * console.error(
2862
+ * `Spend cap reached: spent ${err.spentCents} of ${err.capCents} cents. ` +
2863
+ * `Resets at ${err.resetsAt}.`,
2864
+ * )
2865
+ * }
2866
+ * }
2867
+ * ```
2868
+ */
2869
+ export declare class SpendCapReachedError extends LedewireError {
2870
+ static readonly brand: string;
2871
+ /** The buyer's daily spend cap in cents. */
2872
+ readonly capCents: number;
2873
+ /** Total spent so far in the current spend window. */
2874
+ readonly spentCents: number;
2875
+ /** Cap minus spend so far, floored at zero — what the buyer may still spend in this window. */
2876
+ readonly remainingCents: number;
2877
+ /** ISO 8601 timestamp (UTC) of the instant the current spend window rolls. */
2878
+ readonly resetsAt: string;
2879
+ /**
2880
+ * Whether this buyer's bulk acquisitions are exempt from the cap. When `true`,
2881
+ * `spentCents` and `remainingCents` describe ordinary spend only — an authorized
2882
+ * bulk acquisition does not consume them.
2883
+ */
2884
+ readonly bulkExempt: boolean;
2885
+ constructor(message: string, capInfo: {
2886
+ capCents: number;
2887
+ spentCents: number;
2888
+ remainingCents: number;
2889
+ resetsAt: string;
2890
+ bulkExempt: boolean;
2891
+ }, code?: number);
2892
+ }
2893
+
1659
2894
  /** Internal representation of stored authentication tokens. */
1660
2895
  export declare interface StoredTokens {
1661
2896
  accessToken: string;
@@ -1765,6 +3000,271 @@ export declare interface TokenStorage {
1765
3000
  clearTokens(): void | Promise<void>;
1766
3001
  }
1767
3002
 
3003
+ /** A buyer API key record (secret is never included after creation). */
3004
+ declare type UserApiKey = components['schemas']['UserApiKey'];
3005
+
3006
+ /** Request body for creating a new buyer API key. */
3007
+ declare type UserApiKeyCreateRequest = components['schemas']['UserApiKeyCreateRequest'];
3008
+
3009
+ /**
3010
+ * Response returned once when a buyer API key is created.
3011
+ * The `secret` is shown exactly once and cannot be retrieved again.
3012
+ * Store it immediately in a secrets manager.
3013
+ */
3014
+ declare type UserApiKeyCreateResponse = components['schemas']['UserApiKeyCreateResponse'];
3015
+
3016
+ /**
3017
+ * Manage buyer API keys for the authenticated user.
3018
+ *
3019
+ * Buyer API keys are the authentication credential for autonomous agents — they
3020
+ * allow an agent to obtain a buyer JWT via `auth.loginWithBuyerApiKey()` without
3021
+ * requiring a username and password. Each key is named, independently revocable,
3022
+ * and can carry an optional `spending_limit_cents` ceiling.
3023
+ *
3024
+ * **Secret handling:** `create()` returns the `secret` exactly once. It is never
3025
+ * retrievable again after the response is received — store it immediately in a
3026
+ * secrets manager (e.g. environment variable, Vault, AWS Secrets Manager).
3027
+ *
3028
+ * Obtain via `client.user.apiKeys` — do not construct directly.
3029
+ *
3030
+ * @example
3031
+ * ```ts
3032
+ * // Create a key for an agent, with a $10 spend ceiling
3033
+ * const { key, secret } = await client.user.apiKeys.create({
3034
+ * name: 'my-rag-agent',
3035
+ * spending_limit_cents: 1000,
3036
+ * })
3037
+ * // Store secret immediately — it cannot be retrieved again
3038
+ * await secretsManager.put('LEDEWIRE_BUYER_SECRET', secret)
3039
+ *
3040
+ * // List all keys (secrets never included)
3041
+ * const keys = await client.user.apiKeys.list()
3042
+ *
3043
+ * // Revoke a compromised key
3044
+ * await client.user.apiKeys.revoke(keys[0].id)
3045
+ * ```
3046
+ */
3047
+ declare class UserApiKeysNamespace {
3048
+ private readonly http;
3049
+ /* Excluded from this release type: __constructor */
3050
+ /**
3051
+ * Returns all buyer API keys for the authenticated user.
3052
+ * The `secret` is never included in list responses.
3053
+ *
3054
+ * @returns Array of API key records.
3055
+ */
3056
+ list(): Promise<UserApiKey[]>;
3057
+ /**
3058
+ * Creates a new buyer API key.
3059
+ *
3060
+ * The `secret` in the response is shown exactly once and cannot be retrieved
3061
+ * again. Store it immediately in a secrets manager before discarding the
3062
+ * response object.
3063
+ *
3064
+ * @param body - Name and optional spend ceiling for the new key.
3065
+ * @returns The new key's public identifier and one-time secret.
3066
+ *
3067
+ * @example
3068
+ * ```ts
3069
+ * const { key, secret } = await client.user.apiKeys.create({
3070
+ * name: 'production-agent',
3071
+ * spending_limit_cents: 5000, // $50 cap
3072
+ * })
3073
+ * // ⚠️ Store secret NOW — it is shown once only
3074
+ * process.env.LEDEWIRE_BUYER_SECRET = secret
3075
+ * ```
3076
+ */
3077
+ create(body: UserApiKeyCreateRequest): Promise<UserApiKeyCreateResponse>;
3078
+ /**
3079
+ * Revokes (permanently deletes) a buyer API key by ID.
3080
+ *
3081
+ * Any agent currently using this key will receive `401` on its next
3082
+ * token refresh. Revocation takes effect immediately.
3083
+ *
3084
+ * @param id - UUID of the API key to revoke.
3085
+ */
3086
+ revoke(id: string): Promise<void>;
3087
+ }
3088
+
3089
+ /**
3090
+ * Manage the authenticated buyer's MCP API keys.
3091
+ *
3092
+ * MCP API keys authenticate agent requests against the Ledewire MCP server, sent
3093
+ * as `Authorization: Bearer <key>:<secret>`. Each key carries an explicit set of
3094
+ * scopes — `can_search` (default `true`), `can_purchase` (default `false`), and
3095
+ * the seller-tier `can_manage_content` / `can_read_analytics`, which additionally
3096
+ * require `store_id` to be set to a store the user owns or authors.
3097
+ *
3098
+ * **Secret handling:** `create()` returns the `secret` exactly once. It is never
3099
+ * retrievable again — store it immediately, alongside the `key`, in a secrets
3100
+ * manager.
3101
+ *
3102
+ * **Changing permissions:** scopes are fixed at creation. To change what a key
3103
+ * can do, revoke it and create a replacement with the desired scopes.
3104
+ *
3105
+ * Obtain via `client.user.mcpKeys` — do not construct directly.
3106
+ *
3107
+ * @example
3108
+ * ```ts
3109
+ * // Create a search+purchase key for an autonomous agent
3110
+ * const { key, secret } = await client.user.mcpKeys.create({
3111
+ * label: 'my-rag-agent',
3112
+ * can_search: true,
3113
+ * can_purchase: true,
3114
+ * })
3115
+ * // Store immediately — the secret cannot be retrieved again
3116
+ * await secretsManager.put('LEDEWIRE_MCP_CREDENTIAL', `${key}:${secret}`)
3117
+ *
3118
+ * // List all keys (secrets never included)
3119
+ * const keys = await client.user.mcpKeys.list()
3120
+ *
3121
+ * // To change permissions: revoke and recreate
3122
+ * await client.user.mcpKeys.revoke(keys[0].id)
3123
+ * ```
3124
+ */
3125
+ declare class UserMcpKeysNamespace {
3126
+ private readonly http;
3127
+ /* Excluded from this release type: __constructor */
3128
+ /**
3129
+ * Returns all MCP API keys for the authenticated user.
3130
+ * The `secret` is never included in list responses.
3131
+ *
3132
+ * @returns Array of MCP API key records.
3133
+ */
3134
+ list(): Promise<McpApiKey[]>;
3135
+ /**
3136
+ * Creates a new MCP API key.
3137
+ *
3138
+ * The `secret` in the response is shown exactly once and cannot be retrieved
3139
+ * again. Store it immediately alongside `key` — the pair is used together as
3140
+ * `Authorization: Bearer <key>:<secret>` against the Ledewire MCP server.
3141
+ *
3142
+ * @param body - Label and scopes for the new key.
3143
+ * @returns The new key's public identifier, scopes, and one-time secret.
3144
+ *
3145
+ * @example
3146
+ * ```ts
3147
+ * const { key, secret } = await client.user.mcpKeys.create({
3148
+ * label: 'production-agent',
3149
+ * can_search: true,
3150
+ * can_purchase: true,
3151
+ * })
3152
+ * ```
3153
+ */
3154
+ create(body: McpApiKeyCreateRequest): Promise<McpApiKeyCreateResponse>;
3155
+ /**
3156
+ * Revokes (permanently deletes) an MCP API key by ID.
3157
+ *
3158
+ * To change a key's permissions, revoke it and create a replacement with the
3159
+ * desired scopes — scopes cannot be edited in place.
3160
+ *
3161
+ * @param id - UUID of the MCP API key to revoke.
3162
+ */
3163
+ revoke(id: string): Promise<void>;
3164
+ }
3165
+
3166
+ /**
3167
+ * Authenticated buyer account operations.
3168
+ *
3169
+ * Obtain via `client.user` — do not construct directly.
3170
+ */
3171
+ declare class UserNamespace {
3172
+ /**
3173
+ * Buyer API key management: create, list, and revoke named API keys.
3174
+ * Keys are used by autonomous agents to authenticate without a username/password.
3175
+ */
3176
+ readonly apiKeys: UserApiKeysNamespace;
3177
+ /**
3178
+ * The authenticated buyer's daily spend cap: read and update the ceiling that
3179
+ * governs every wallet debit (MCP, REST, and the web payment gate).
3180
+ */
3181
+ readonly spendCap: UserSpendCapNamespace;
3182
+ /**
3183
+ * MCP API key management: create, list, and revoke keys scoped for use against
3184
+ * the Ledewire MCP server.
3185
+ */
3186
+ readonly mcpKeys: UserMcpKeysNamespace;
3187
+ /* Excluded from this release type: __constructor */
3188
+ }
3189
+
3190
+ /**
3191
+ * The authenticated buyer's daily spend cap, read against the current spend window.
3192
+ * The cap governs every wallet debit the buyer makes — MCP, REST, or the web payment
3193
+ * gate — and spend is derived from completed purchases, so a refund returns allowance.
3194
+ *
3195
+ * `cap_cents`, `spent_cents`, `remaining_cents` and `resets_at` are spelled exactly as
3196
+ * they are in {@link DailySpendCapReachedErrorBody}, so a refusal and this resource
3197
+ * describe the same numbers.
3198
+ */
3199
+ export declare type UserSpendCap = components['schemas']['UserSpendCap'];
3200
+
3201
+ /**
3202
+ * Manage the authenticated buyer's daily spend cap.
3203
+ *
3204
+ * Every new buyer starts with a default cap, so `null` on {@link
3205
+ * UserSpendCapNamespace.update} is the only way to become uncapped — there is no
3206
+ * separate "remove the cap" call. While capped, `cap_cents` and `remaining_cents`
3207
+ * are numbers; once uncapped, both read back as `null` because there is no ceiling
3208
+ * to report or subtract from.
3209
+ *
3210
+ * The cap governs every wallet debit the buyer makes — MCP, REST, or the web
3211
+ * payment gate — over a rolling window bounded by `spend_window_timezone` (an IANA
3212
+ * zone, UTC by default) and reset at `resets_at`. `bulk_exempt` says whether this
3213
+ * buyer's bulk acquisitions are excluded from that spend, in which case
3214
+ * `spent_cents` / `remaining_cents` describe ordinary purchases only.
3215
+ *
3216
+ * **Exceeding the cap throws {@link SpendCapReachedError}** (HTTP 402) from
3217
+ * `client.purchases.create()` and the x402 content gate. Funding the wallet does not
3218
+ * clear it — the buyer must wait for the window to roll at `resets_at`, or the cap
3219
+ * must be raised or cleared here.
3220
+ *
3221
+ * Obtain via `client.user.spendCap` — do not construct directly.
3222
+ *
3223
+ * @example
3224
+ * ```ts
3225
+ * const cap = await client.user.spendCap.get()
3226
+ * if (cap.remaining_cents !== null && cap.remaining_cents < 500) {
3227
+ * console.warn(`Only ${cap.remaining_cents}c left before the cap resets at ${cap.resets_at}`)
3228
+ * }
3229
+ *
3230
+ * // Raise the cap to $20/day
3231
+ * await client.user.spendCap.update({ daily_spend_limit_cents: 2000 })
3232
+ *
3233
+ * // Remove the cap entirely (become uncapped)
3234
+ * await client.user.spendCap.update({ daily_spend_limit_cents: null })
3235
+ * ```
3236
+ */
3237
+ declare class UserSpendCapNamespace {
3238
+ private readonly http;
3239
+ /* Excluded from this release type: __constructor */
3240
+ /**
3241
+ * Returns the authenticated buyer's spend cap, read against the current spend
3242
+ * window.
3243
+ *
3244
+ * @returns The current spend cap, spend-to-date, and reset time.
3245
+ */
3246
+ get(): Promise<UserSpendCap>;
3247
+ /**
3248
+ * Sets or clears the authenticated buyer's daily spend cap.
3249
+ *
3250
+ * `daily_spend_limit_cents` is required and nullable: pass a number to set the
3251
+ * cap, or `null` to remove it (the only way to become uncapped). A cap set below
3252
+ * spend already made in the current window is accepted — it just leaves
3253
+ * `remaining_cents` at zero until the window rolls.
3254
+ *
3255
+ * @param body - The new cap in whole cents, or `null` to remove it.
3256
+ * @returns The updated spend cap.
3257
+ */
3258
+ update(body: UserSpendCapUpdateRequest): Promise<UserSpendCap>;
3259
+ }
3260
+
3261
+ /**
3262
+ * Request body for `PATCH /v1/user/spend-cap`. `daily_spend_limit_cents` is required
3263
+ * and nullable: `null` is how a buyer becomes uncapped, and an omitted field is a
3264
+ * client error rather than a request to be uncapped.
3265
+ */
3266
+ export declare type UserSpendCapUpdateRequest = components['schemas']['UserSpendCapUpdateRequest'];
3267
+
1768
3268
  /** Current wallet balance for the authenticated buyer. */
1769
3269
  export declare type WalletBalanceResponse = components['schemas']['WalletBalanceResponse'];
1770
3270