@ledewire/browser 0.7.0 → 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,7 +44,8 @@ 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). */
@@ -291,8 +292,10 @@ declare class BrowserConfigNamespace {
291
292
  * @example
292
293
  * ```ts
293
294
  * const result = await lw.content.getWithAccess('content-id')
294
- * if (result.access_info.next_required_action === 'view_content') {
295
- * 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.
296
299
  * }
297
300
  * ```
298
301
  */
@@ -311,8 +314,10 @@ declare class BrowserContentNamespace {
311
314
  * @example
312
315
  * ```ts
313
316
  * const result = await lw.content.getWithAccess('article-123')
314
- * if (result.access_info.next_required_action === 'view_content') {
315
- * 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.
316
321
  * }
317
322
  * ```
318
323
  */
@@ -322,6 +327,11 @@ declare class BrowserContentNamespace {
322
327
  /**
323
328
  * Buyer purchases namespace — create and retrieve content purchases.
324
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
+ *
325
335
  * Obtain via `lw.purchases` — do not construct directly.
326
336
  *
327
337
  * @example
@@ -334,10 +344,50 @@ declare class BrowserPurchasesNamespace {
334
344
  protected readonly http: HttpClient;
335
345
  constructor(http: HttpClient);
336
346
  /**
337
- * 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.
338
360
  *
339
361
  * @param body - The content ID and expected price in cents.
340
- * @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
+ * ```
341
391
  */
342
392
  create(body: PurchaseCreateRequest): Promise<PurchaseResponse>;
343
393
  /**
@@ -482,12 +532,33 @@ declare class BrowserWalletNamespace {
482
532
  /**
483
533
  * Returns the authenticated buyer's current wallet balance.
484
534
  *
485
- * @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.
486
545
  */
487
546
  balance(): Promise<WalletBalanceResponse>;
488
547
  /**
489
548
  * Returns the authenticated buyer's wallet transaction history, newest first.
490
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
+ *
491
562
  * @returns A list of completed wallet transaction entries.
492
563
  */
493
564
  transactions(): Promise<WalletTransactionItem[]>;
@@ -516,7 +587,12 @@ declare class BrowserWalletNamespace {
516
587
  * ```ts
517
588
  * const state = await lw.checkout.state('content-id')
518
589
  * // state.checkout_state.next_required_action:
519
- * // '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.
520
596
  * ```
521
597
  */
522
598
  declare class CheckoutNamespace {
@@ -537,11 +613,18 @@ declare class CheckoutNamespace {
537
613
  }
538
614
 
539
615
  /**
540
- * Next step in a content checkout flow.
541
- * Extends `NextRequiredAction` with the terminal `view_content` state
542
- * (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.
543
626
  */
544
- export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
627
+ export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase';
545
628
 
546
629
  /**
547
630
  * Checkout state machine result for a specific content item, as returned by
@@ -573,7 +656,7 @@ declare interface components {
573
656
  has_sufficient_funds: boolean;
574
657
  wallet_balance_cents: number;
575
658
  /** @enum {string} */
576
- next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'none';
659
+ next_required_action: 'authenticate' | 'fund_wallet' | 'purchase';
577
660
  };
578
661
  AuthenticationResponse: {
579
662
  /** @enum {string} */
@@ -646,6 +729,11 @@ declare interface components {
646
729
  error: {
647
730
  code: number;
648
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';
649
737
  };
650
738
  };
651
739
  AuthSignupRequest: {
@@ -705,6 +793,396 @@ declare interface components {
705
793
  /** @description 64-char hex authentication secret. Store immediately — shown once only. */
706
794
  secret: string;
707
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
+ };
708
1186
  AuthTokenRefreshRequest: {
709
1187
  refresh_token?: string;
710
1188
  };
@@ -794,8 +1272,26 @@ declare interface components {
794
1272
  [key: string]: unknown;
795
1273
  };
796
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. */
797
1276
  WalletBalanceResponse: {
1277
+ /** @description Spendable balance in cents. Excludes funds held against a bulk acquisition. */
798
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
+ }[];
799
1295
  };
800
1296
  WalletTransactionItem: {
801
1297
  /** @description ID of the transaction entry (matches the source record) */
@@ -806,17 +1302,20 @@ declare interface components {
806
1302
  */
807
1303
  type: 'credit' | 'debit';
808
1304
  /**
809
- * @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.
810
1306
  * @enum {string}
811
1307
  */
812
- reason: 'wallet_funding' | 'purchase' | 'refund';
813
- /** @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. */
814
1310
  amount_cents: number;
815
1311
  /** @description Running wallet balance immediately after this event */
816
1312
  balance_after_cents: number;
817
- /** @enum {string} */
818
- status: 'completed' | 'pending' | 'failed' | 'cancelled';
819
- /** @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) */
820
1319
  reference_id: string;
821
1320
  /** @description Human-readable label suitable for display */
822
1321
  description: string;
@@ -842,17 +1341,17 @@ declare interface components {
842
1341
  has_sufficient_funds?: boolean;
843
1342
  };
844
1343
  };
845
- /** @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`. */
846
1345
  ContentUpdateRequest: {
847
1346
  /** @description Content title */
848
1347
  title?: string;
849
1348
  /**
850
1349
  * Format: byte
851
- * @description Full article body in markdown, base64 encoded. For `markdown` content only. Must be base64-encoded before sending (e.g. `btoa(markdownText)`).
1350
+ * @description Full article body, base64 encoded. For `markdown` and inline `html` content. Must be base64-encoded before sending (e.g. `btoa(bodyText)`).
852
1351
  */
853
1352
  content_body?: string;
854
1353
  /**
855
- * @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.
856
1355
  * @example https://vimeo.com/123456789
857
1356
  */
858
1357
  content_uri?: string;
@@ -1084,22 +1583,22 @@ declare interface components {
1084
1583
  */
1085
1584
  started_at?: string;
1086
1585
  };
1087
- /** @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). */
1088
1587
  Content: {
1089
1588
  /**
1090
- * @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.
1091
1590
  * @enum {string}
1092
1591
  */
1093
- content_type: 'markdown' | 'external_ref';
1592
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref';
1094
1593
  /** @description Content title */
1095
1594
  title: string;
1096
1595
  /**
1097
1596
  * Format: byte
1098
- * @description Full article body in markdown, base64 encoded. Required when `content_type` is `markdown`. Must be base64-encoded before sending (e.g. `btoa(markdownText)`).
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.
1099
1598
  */
1100
1599
  content_body?: string;
1101
1600
  /**
1102
- * @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`.
1103
1602
  * @example https://vimeo.com/123456789
1104
1603
  */
1105
1604
  content_uri?: string;
@@ -1130,23 +1629,23 @@ declare interface components {
1130
1629
  [key: string]: unknown;
1131
1630
  };
1132
1631
  };
1133
- /** @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`. */
1134
1633
  ContentResponse: {
1135
1634
  id: string;
1136
1635
  /**
1137
- * @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.
1138
1637
  * @enum {string}
1139
1638
  */
1140
- content_type: 'markdown' | 'external_ref';
1639
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1141
1640
  /** @description Content title */
1142
1641
  title: string;
1143
1642
  /**
1144
1643
  * Format: byte
1145
- * @description Full article body in markdown, base64 encoded. Present when `content_type` is `markdown`. Must be base64-decoded before rendering (e.g. `atob(content.content_body ?? '')`).
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.
1146
1645
  */
1147
1646
  content_body?: string | null;
1148
1647
  /**
1149
- * @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`.
1150
1649
  * @example https://vimeo.com/123456789
1151
1650
  */
1152
1651
  content_uri?: string | null;
@@ -1250,7 +1749,7 @@ declare interface components {
1250
1749
  ContentListItem: {
1251
1750
  id: string;
1252
1751
  /** @enum {string} */
1253
- content_type: 'markdown' | 'external_ref';
1752
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1254
1753
  title: string;
1255
1754
  price_cents: number;
1256
1755
  /**
@@ -1272,7 +1771,7 @@ declare interface components {
1272
1771
  /**
1273
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.
1274
1773
  *
1275
- * **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.
1276
1775
  */
1277
1776
  ContentWithAccessResponse: components['schemas']['ContentResponse'] & {
1278
1777
  access_info: components['schemas']['ContentAccessInfo'];
@@ -1308,6 +1807,17 @@ declare interface components {
1308
1807
  /** Format: date-time */
1309
1808
  timestamp: string;
1310
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;
1311
1821
  };
1312
1822
  MerchantSaleResponse: {
1313
1823
  id: string;
@@ -1364,7 +1874,7 @@ declare interface components {
1364
1874
  has_sufficient_funds?: boolean | null;
1365
1875
  has_purchased: boolean;
1366
1876
  /** @enum {string} */
1367
- next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
1877
+ next_required_action: 'authenticate' | 'fund_wallet' | 'purchase';
1368
1878
  };
1369
1879
  };
1370
1880
  WalletPaymentStatusResponse: {
@@ -1392,14 +1902,45 @@ declare interface components {
1392
1902
  };
1393
1903
  };
1394
1904
  };
1395
- /** @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`; `content_uri` is present when `content_type` is `external_ref`. `purchase_id` is the UUID of the settled Purchase record (null for free content). */
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). */
1396
1937
  X402ContentResponse: {
1397
1938
  id: string;
1398
1939
  /** @enum {string} */
1399
- content_type: 'markdown' | 'external_ref';
1940
+ content_type: 'markdown' | 'html' | 'pdf' | 'image' | 'video' | 'external_ref' | 'brokered';
1400
1941
  title: string;
1401
1942
  price_cents: number;
1402
- /** Format: byte */
1943
+ /** @description Plain UTF-8 text preview. */
1403
1944
  teaser: string;
1404
1945
  /** @enum {string} */
1405
1946
  visibility: 'public' | 'unlisted' | 'private';
@@ -1411,12 +1952,9 @@ declare interface components {
1411
1952
  purchase_id?: string | null;
1412
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. */
1413
1954
  resource_url?: string | null;
1414
- /**
1415
- * Format: byte
1416
- * @description Full article body in markdown. Present when content_type is markdown.
1417
- */
1955
+ /** @description Full article body in plain UTF-8 markdown. Present when content_type is markdown. */
1418
1956
  content_body?: string | null;
1419
- /** @description URI of the external resource. Present when content_type is external_ref. */
1957
+ /** @description URI of the external resource. Present when content_type is `external_ref`, `pdf`, `image`, or `video`. */
1420
1958
  content_uri?: string | null;
1421
1959
  };
1422
1960
  /** @description Returned when the URL matches a registered Ledewire content item. */
@@ -1454,6 +1992,280 @@ declare interface components {
1454
1992
  e: string;
1455
1993
  }[];
1456
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
+ };
1457
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. */
1458
2270
  SettlementResponse: {
1459
2271
  /** @enum {boolean} */
@@ -1512,6 +2324,69 @@ declare interface components {
1512
2324
  /** Format: date-time */
1513
2325
  created_at: string;
1514
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
+ };
1515
2390
  };
1516
2391
  responses: never;
1517
2392
  parameters: never;
@@ -1529,6 +2404,37 @@ export declare type ContentResponse = components['schemas']['ContentResponse'];
1529
2404
  /** A piece of content with buyer access information. */
1530
2405
  export declare type ContentWithAccessResponse = components['schemas']['ContentWithAccessResponse'];
1531
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
+
1532
2438
  /**
1533
2439
  * Thrown when the authenticated user does not have permission
1534
2440
  * to perform the requested operation.
@@ -1580,7 +2486,8 @@ export declare type ContentWithAccessResponse = components['schemas']['ContentWi
1580
2486
  * ```
1581
2487
  */
1582
2488
  export declare class ForbiddenError extends LedewireError {
1583
- constructor(message: string, code?: number);
2489
+ static readonly brand: string;
2490
+ constructor(message: string, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
1584
2491
  }
1585
2492
 
1586
2493
  /**
@@ -1590,6 +2497,8 @@ export declare class ForbiddenError extends LedewireError {
1590
2497
  * - Injects `Authorization: Bearer <token>` headers automatically
1591
2498
  * - Maps HTTP error responses to typed `LedewireError` subclasses
1592
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
1593
2502
  *
1594
2503
  * This is an internal class - consumers should use the
1595
2504
  * package-level client factories (`init` / `createClient`) instead.
@@ -1603,8 +2512,9 @@ declare class HttpClient {
1603
2512
  * GET request with optional query parameters.
1604
2513
  * @param path - API path (e.g. `/v1/wallet/balance`)
1605
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.
1606
2516
  */
1607
- 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>;
1608
2518
  /**
1609
2519
  * POST request.
1610
2520
  * @param path - API path
@@ -1629,8 +2539,28 @@ declare class HttpClient {
1629
2539
  * @param path - API path
1630
2540
  */
1631
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>;
1632
2557
  private buildUrl;
1633
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;
1634
2564
  private throwApiError;
1635
2565
  }
1636
2566
 
@@ -1679,6 +2609,11 @@ export declare function init(config: BrowserClientConfig): BrowserClient;
1679
2609
  * All errors thrown by the SDK are instances of this class,
1680
2610
  * making it safe to use `err instanceof LedewireError` as a type guard.
1681
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
+ *
1682
2617
  * @example
1683
2618
  * ```ts
1684
2619
  * try {
@@ -1691,11 +2626,48 @@ export declare function init(config: BrowserClientConfig): BrowserClient;
1691
2626
  * ```
1692
2627
  */
1693
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;
1694
2636
  /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */
1695
2637
  readonly statusCode: number;
1696
2638
  /** Machine-readable error code from the API error body, if present. */
1697
2639
  readonly code: number | undefined;
1698
- 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;
1699
2671
  }
1700
2672
 
1701
2673
  /**
@@ -1724,6 +2696,18 @@ export declare class LedewireError extends Error {
1724
2696
  */
1725
2697
  export declare function localStorageAdapter(key?: string): TokenStorage;
1726
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
+
1727
2711
  /**
1728
2712
  * In-memory token storage (default for both packages).
1729
2713
  * Tokens are cleared when the page unloads or the process exits.
@@ -1743,7 +2727,8 @@ export declare type NextRequiredAction = ContentAccessInfo['next_required_action
1743
2727
  * Thrown when the requested resource does not exist.
1744
2728
  */
1745
2729
  export declare class NotFoundError extends LedewireError {
1746
- constructor(message: string, code?: number);
2730
+ static readonly brand: string;
2731
+ constructor(message: string, code?: number, type?: ErrorTypeValue, details?: Record<string, unknown>);
1747
2732
  }
1748
2733
 
1749
2734
  /**
@@ -1785,12 +2770,26 @@ export declare type PurchaseCreateRequest = components['schemas']['PurchaseCreat
1785
2770
  * failure, such as a price mismatch or a duplicate purchase.
1786
2771
  */
1787
2772
  export declare class PurchaseError extends LedewireError {
1788
- 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>);
1789
2775
  }
1790
2776
 
1791
2777
  /** A purchase record. */
1792
2778
  export declare type PurchaseResponse = components['schemas']['PurchaseResponse'];
1793
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
+
1794
2793
  /**
1795
2794
  * Search criteria for seller content search.
1796
2795
  * At least one field must be supplied. All supplied fields must match (AND logic).
@@ -1839,6 +2838,59 @@ export declare interface SellerContentSearchRequest {
1839
2838
  */
1840
2839
  export declare function sessionStorageAdapter(key?: string): TokenStorage;
1841
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
+
1842
2894
  /** Internal representation of stored authentication tokens. */
1843
2895
  export declare interface StoredTokens {
1844
2896
  accessToken: string;
@@ -2034,6 +3086,83 @@ declare class UserApiKeysNamespace {
2034
3086
  revoke(id: string): Promise<void>;
2035
3087
  }
2036
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
+
2037
3166
  /**
2038
3167
  * Authenticated buyer account operations.
2039
3168
  *
@@ -2045,9 +3174,97 @@ declare class UserNamespace {
2045
3174
  * Keys are used by autonomous agents to authenticate without a username/password.
2046
3175
  */
2047
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;
2048
3187
  /* Excluded from this release type: __constructor */
2049
3188
  }
2050
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
+
2051
3268
  /** Current wallet balance for the authenticated buyer. */
2052
3269
  export declare type WalletBalanceResponse = components['schemas']['WalletBalanceResponse'];
2053
3270