@frihet/mcp-server 1.16.6 → 1.17.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.
Files changed (68) hide show
  1. package/README.md +92 -31
  2. package/dist/agent-onboarding.d.ts +98 -0
  3. package/dist/agent-onboarding.js +275 -0
  4. package/dist/api-origin.d.ts +9 -0
  5. package/dist/api-origin.js +41 -0
  6. package/dist/canary/mcp-harness.d.ts +77 -0
  7. package/dist/canary/mcp-harness.js +549 -0
  8. package/dist/capability-truth.d.ts +29 -0
  9. package/dist/capability-truth.js +254 -0
  10. package/dist/client-interface.d.ts +22 -14
  11. package/dist/client.d.ts +114 -18
  12. package/dist/client.js +488 -56
  13. package/dist/demo-client.d.ts +16 -13
  14. package/dist/demo-client.js +171 -17
  15. package/dist/demo-fixtures.d.ts +3 -3
  16. package/dist/demo-fixtures.js +7 -7
  17. package/dist/fiscal-aliases.d.ts +44 -0
  18. package/dist/fiscal-aliases.js +62 -0
  19. package/dist/index.js +41 -56
  20. package/dist/logger.d.ts +2 -2
  21. package/dist/logger.js +140 -8
  22. package/dist/observability.d.ts +18 -29
  23. package/dist/observability.js +192 -105
  24. package/dist/openai-profile.d.ts +25 -21
  25. package/dist/openai-profile.js +1166 -156
  26. package/dist/openai-review-contract.d.ts +15 -6
  27. package/dist/openai-review-contract.js +715 -41
  28. package/dist/openai-review-oauth.d.ts +47 -0
  29. package/dist/openai-review-oauth.js +73 -3
  30. package/dist/prompts/register-all.d.ts +2 -0
  31. package/dist/prompts/register-all.js +2 -0
  32. package/dist/public-capability-contract.d.ts +39 -0
  33. package/dist/public-capability-contract.js +186 -0
  34. package/dist/redaction.d.ts +10 -0
  35. package/dist/redaction.js +74 -0
  36. package/dist/resources/register-all.js +6 -12
  37. package/dist/server-composition.d.ts +16 -0
  38. package/dist/server-composition.js +44 -0
  39. package/dist/tool-exposure.d.ts +25 -22
  40. package/dist/tool-exposure.js +199 -77
  41. package/dist/tools/accountingClose.d.ts +14 -5
  42. package/dist/tools/accountingClose.js +41 -23
  43. package/dist/tools/backend-availability.d.ts +3 -3
  44. package/dist/tools/backend-availability.js +3 -3
  45. package/dist/tools/clients.js +9 -9
  46. package/dist/tools/crm.js +22 -21
  47. package/dist/tools/deposits.js +30 -11
  48. package/dist/tools/einvoice.d.ts +1 -1
  49. package/dist/tools/expenses.d.ts +16 -0
  50. package/dist/tools/expenses.js +47 -27
  51. package/dist/tools/hr.d.ts +2 -3
  52. package/dist/tools/hr.js +8 -9
  53. package/dist/tools/intelligence.js +2 -2
  54. package/dist/tools/invoices.js +150 -47
  55. package/dist/tools/payroll.d.ts +5 -8
  56. package/dist/tools/payroll.js +19 -32
  57. package/dist/tools/permissions.d.ts +4 -7
  58. package/dist/tools/permissions.js +11 -14
  59. package/dist/tools/products.js +6 -6
  60. package/dist/tools/quotes.js +109 -24
  61. package/dist/tools/shared.d.ts +1431 -75
  62. package/dist/tools/shared.js +386 -76
  63. package/dist/tools/team.js +18 -14
  64. package/dist/tools/vendors.js +9 -9
  65. package/dist/tools/webhooks.js +35 -30
  66. package/dist/types.d.ts +28 -4
  67. package/docs/agent-onboarding.json +381 -0
  68. package/package.json +30 -7
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Public capability truth for the full Frihet MCP surfaces.
3
+ *
4
+ * Registration proves only that a tool name and handler exist. It does not
5
+ * prove that the backing API family is deployed, enabled for a workspace, or
6
+ * authorized for the caller. This profile adds a conservative, machine-readable
7
+ * fact to tools/list and corrects action annotations on the non-OpenAI surface.
8
+ *
9
+ * The OpenAI review profile reuses the same annotation corrections so every
10
+ * public surface reports side effects consistently.
11
+ */
12
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
13
+ export declare const CAPABILITY_META_KEY = "io.frihet/capability";
14
+ export type CapabilityCallability = "api_dependent" | "runtime_checked" | "deferred" | "unavailable" | "local";
15
+ export type ExternalSideEffect = "email_or_invitation" | "webhook_delivery_or_configuration" | "fiscal_or_einvoice_submission" | "external_provider_read" | "external_provider_configuration" | "money_movement" | "channel_sync";
16
+ export interface PublicCapabilityTruth {
17
+ readonly registered: true;
18
+ readonly callability: CapabilityCallability;
19
+ readonly canonicalOperation: string;
20
+ readonly writesFrihet: boolean;
21
+ readonly externalInteraction: boolean;
22
+ readonly externalSideEffects: readonly ExternalSideEffect[];
23
+ }
24
+ export declare function correctToolAnnotations(name: string, annotations: ToolAnnotations | undefined): ToolAnnotations;
25
+ export declare function buildPublicCapabilityTruth(name: string, annotations: ToolAnnotations | undefined): PublicCapabilityTruth;
26
+ export declare function buildLocalDiscoveryCapability(name: string): PublicCapabilityTruth;
27
+ /** Must run after grouped exposure (when grouped) and before registration. */
28
+ export declare function applyPublicCapabilityTruth(server: any): void;
29
+ //# sourceMappingURL=capability-truth.d.ts.map
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Public capability truth for the full Frihet MCP surfaces.
3
+ *
4
+ * Registration proves only that a tool name and handler exist. It does not
5
+ * prove that the backing API family is deployed, enabled for a workspace, or
6
+ * authorized for the caller. This profile adds a conservative, machine-readable
7
+ * fact to tools/list and corrects action annotations on the non-OpenAI surface.
8
+ *
9
+ * The OpenAI review profile reuses the same annotation corrections so every
10
+ * public surface reports side effects consistently.
11
+ */
12
+ export const CAPABILITY_META_KEY = "io.frihet/capability";
13
+ const RUNTIME_CHECKED = new Set([
14
+ "anomaly_list",
15
+ "attendance_clock_in",
16
+ "attendance_clock_out",
17
+ "frihet_aiem_calculate",
18
+ "frihet_bank_rule_create",
19
+ "frihet_bank_rules_list",
20
+ "frihet_gl_entry_approve",
21
+ "frihet_gl_entry_audit_log",
22
+ "frihet_gl_entry_reject",
23
+ "frihet_modelo_200_summary",
24
+ "frihet_modelo_202_summary",
25
+ "frihet_modelo_415_summary",
26
+ "frihet_modelo_418_summary",
27
+ "frihet_modelo_425_summary",
28
+ "frihet_portal_domain_add",
29
+ "frihet_portal_domain_remove",
30
+ "frihet_portal_domain_verify",
31
+ "frihet_portal_onboard_link_generate",
32
+ "frihet_tax_id_vies_lookup",
33
+ "gestoria_template_create",
34
+ "get_modelo_130_summary",
35
+ "get_modelo_180_summary",
36
+ "get_modelo_303_summary",
37
+ "get_modelo_347_summary",
38
+ "get_modelo_390_summary",
39
+ "leave_approve",
40
+ "leave_cancel",
41
+ "leave_list",
42
+ "leave_reject",
43
+ "leave_request_create",
44
+ "onboarding_persona_set",
45
+ "onboarding_status",
46
+ "overtime_report",
47
+ "payroll_checklist",
48
+ "payroll_export",
49
+ "period_close_status",
50
+ "permissions_matrix",
51
+ "permissions_me",
52
+ "verifactu_resubmit",
53
+ "verifactu_status",
54
+ "send_einvoice",
55
+ "get_einvoice_status",
56
+ "validate_einvoice_xml",
57
+ "export_datev",
58
+ "einvoice_export",
59
+ "face_submit",
60
+ "face_status",
61
+ "ticketbai_submit",
62
+ "ticketbai_status",
63
+ ]);
64
+ const DEFERRED = new Set([
65
+ "period_close",
66
+ "period_reopen",
67
+ "gestoria_message_send",
68
+ "gestoria_messages_list",
69
+ "gestoria_template_bulk_send",
70
+ "gestoria_aging_consolidated",
71
+ "create_reservation",
72
+ "sync_channel",
73
+ ]);
74
+ const UNAVAILABLE = new Set(["ksef_submit"]);
75
+ const DESTRUCTIVE_UPDATES = new Set([
76
+ // These writes can synchronously enqueue delivery to workspace webhooks.
77
+ // Once an external endpoint receives the event, that disclosure cannot be
78
+ // recalled even if the Frihet record itself remains editable.
79
+ "create_invoice",
80
+ "duplicate_invoice",
81
+ "create_credit_note",
82
+ "create_expense",
83
+ "create_quote",
84
+ "create_client",
85
+ "log_client_activity",
86
+ "create_product",
87
+ "frihet_gl_entry_approve",
88
+ "frihet_gl_entry_reject",
89
+ "send_einvoice",
90
+ "categorize_transaction",
91
+ "update_client",
92
+ "update_deposit",
93
+ "apply_deposit",
94
+ "refund_deposit",
95
+ "update_expense",
96
+ "leave_approve",
97
+ "leave_reject",
98
+ "leave_cancel",
99
+ "attendance_clock_out",
100
+ "update_invoice",
101
+ "send_invoice",
102
+ "mark_invoice_paid",
103
+ "update_kitchen_ticket",
104
+ "onboarding_persona_set",
105
+ "update_product",
106
+ "update_quote",
107
+ "send_quote",
108
+ "update_recurring_invoice",
109
+ "sync_channel",
110
+ "update_team_member_role",
111
+ "update_time_entry",
112
+ "update_vendor",
113
+ "update_webhook",
114
+ "match_transaction_to_invoice",
115
+ "verifactu_resubmit",
116
+ "pause_recurring_invoice",
117
+ "resume_recurring_invoice",
118
+ "run_recurring_now",
119
+ "frihet_portal_domain_verify",
120
+ "face_submit",
121
+ "ticketbai_submit",
122
+ "frihet_portal_domain_add",
123
+ ]);
124
+ const NON_IDEMPOTENT = new Set([
125
+ "create_invoice",
126
+ "duplicate_invoice",
127
+ "create_credit_note",
128
+ "update_invoice",
129
+ "mark_invoice_paid",
130
+ "delete_invoice",
131
+ "create_expense",
132
+ "update_expense",
133
+ "create_quote",
134
+ "update_quote",
135
+ "delete_quote",
136
+ "create_client",
137
+ "log_client_activity",
138
+ "update_client",
139
+ "create_product",
140
+ "update_product",
141
+ "update_vendor",
142
+ "send_invoice",
143
+ "send_quote",
144
+ "sync_channel",
145
+ "verifactu_resubmit",
146
+ "frihet_portal_domain_verify",
147
+ "pause_recurring_invoice",
148
+ "resume_recurring_invoice",
149
+ ]);
150
+ const WRITE_OVERRIDES = new Set(["frihet_portal_domain_verify"]);
151
+ const EXTERNAL_SIDE_EFFECTS = {
152
+ create_invoice: ["webhook_delivery_or_configuration", "fiscal_or_einvoice_submission"],
153
+ duplicate_invoice: ["webhook_delivery_or_configuration"],
154
+ create_credit_note: ["webhook_delivery_or_configuration"],
155
+ update_invoice: ["webhook_delivery_or_configuration", "fiscal_or_einvoice_submission"],
156
+ mark_invoice_paid: ["webhook_delivery_or_configuration", "fiscal_or_einvoice_submission"],
157
+ delete_invoice: ["webhook_delivery_or_configuration", "fiscal_or_einvoice_submission"],
158
+ create_expense: ["webhook_delivery_or_configuration"],
159
+ update_expense: ["webhook_delivery_or_configuration"],
160
+ create_quote: ["webhook_delivery_or_configuration"],
161
+ update_quote: ["webhook_delivery_or_configuration"],
162
+ delete_quote: ["webhook_delivery_or_configuration"],
163
+ create_client: ["webhook_delivery_or_configuration"],
164
+ log_client_activity: ["webhook_delivery_or_configuration"],
165
+ update_client: ["webhook_delivery_or_configuration"],
166
+ create_product: ["webhook_delivery_or_configuration"],
167
+ update_product: ["webhook_delivery_or_configuration"],
168
+ send_invoice: [
169
+ "email_or_invitation",
170
+ "webhook_delivery_or_configuration",
171
+ "fiscal_or_einvoice_submission",
172
+ ],
173
+ send_quote: ["email_or_invitation", "webhook_delivery_or_configuration"],
174
+ invite_team_member: ["email_or_invitation"],
175
+ create_webhook: ["webhook_delivery_or_configuration"],
176
+ update_webhook: ["webhook_delivery_or_configuration"],
177
+ delete_webhook: ["webhook_delivery_or_configuration"],
178
+ test_webhook: ["webhook_delivery_or_configuration"],
179
+ send_einvoice: ["fiscal_or_einvoice_submission"],
180
+ face_submit: ["fiscal_or_einvoice_submission"],
181
+ face_status: ["external_provider_read"],
182
+ ticketbai_submit: ["fiscal_or_einvoice_submission"],
183
+ verifactu_resubmit: ["fiscal_or_einvoice_submission"],
184
+ gestoria_message_send: ["email_or_invitation"],
185
+ gestoria_template_bulk_send: ["email_or_invitation"],
186
+ frihet_tax_id_vies_lookup: ["external_provider_read"],
187
+ frihet_portal_domain_add: ["external_provider_configuration"],
188
+ frihet_portal_domain_verify: [
189
+ "external_provider_read",
190
+ "external_provider_configuration",
191
+ ],
192
+ frihet_portal_domain_remove: ["external_provider_configuration"],
193
+ refund_sale: ["money_movement"],
194
+ sync_channel: ["channel_sync"],
195
+ };
196
+ function callabilityFor(name) {
197
+ if (UNAVAILABLE.has(name))
198
+ return "unavailable";
199
+ if (DEFERRED.has(name))
200
+ return "deferred";
201
+ if (RUNTIME_CHECKED.has(name))
202
+ return "runtime_checked";
203
+ return "api_dependent";
204
+ }
205
+ export function correctToolAnnotations(name, annotations) {
206
+ const externalInteraction = Object.hasOwn(EXTERNAL_SIDE_EFFECTS, name);
207
+ return {
208
+ ...annotations,
209
+ ...(WRITE_OVERRIDES.has(name) ? { readOnlyHint: false } : {}),
210
+ ...(DESTRUCTIVE_UPDATES.has(name) ? { destructiveHint: true } : {}),
211
+ ...(NON_IDEMPOTENT.has(name) ? { idempotentHint: false } : {}),
212
+ ...(externalInteraction ? { openWorldHint: true } : {}),
213
+ };
214
+ }
215
+ export function buildPublicCapabilityTruth(name, annotations) {
216
+ const effects = Object.hasOwn(EXTERNAL_SIDE_EFFECTS, name)
217
+ ? EXTERNAL_SIDE_EFFECTS[name]
218
+ : [];
219
+ return {
220
+ registered: true,
221
+ callability: callabilityFor(name),
222
+ canonicalOperation: name,
223
+ writesFrihet: annotations?.readOnlyHint !== true,
224
+ externalInteraction: effects.length > 0,
225
+ externalSideEffects: effects,
226
+ };
227
+ }
228
+ export function buildLocalDiscoveryCapability(name) {
229
+ return {
230
+ registered: true,
231
+ callability: "local",
232
+ canonicalOperation: name,
233
+ writesFrihet: false,
234
+ externalInteraction: false,
235
+ externalSideEffects: [],
236
+ };
237
+ }
238
+ /** Must run after grouped exposure (when grouped) and before registration. */
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
+ export function applyPublicCapabilityTruth(server) {
241
+ const originalRegisterTool = server.registerTool.bind(server);
242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
+ server.registerTool = (name, config, handler) => {
244
+ const annotations = correctToolAnnotations(name, config?.annotations);
245
+ const capability = buildPublicCapabilityTruth(name, annotations);
246
+ config.annotations = annotations;
247
+ config._meta = {
248
+ ...(config?._meta ?? {}),
249
+ [CAPABILITY_META_KEY]: capability,
250
+ };
251
+ return originalRegisterTool(name, config, handler);
252
+ };
253
+ }
254
+ //# sourceMappingURL=capability-truth.js.map
@@ -5,7 +5,7 @@
5
5
  * classes satisfy this interface via structural typing. Tool registration
6
6
  * functions depend on this interface so they can work with either client.
7
7
  */
8
- import type { PaginatedResponse } from "./types.js";
8
+ import type { CreateWebhookInput, CreateWebhookResult, PaginatedResponse, UpdateWebhookInput, Webhook } from "./types.js";
9
9
  export interface IFrihetClient {
10
10
  listInvoices(params?: {
11
11
  limit?: number;
@@ -21,7 +21,12 @@ export interface IFrihetClient {
21
21
  getInvoice(id: string): Promise<Record<string, unknown>>;
22
22
  createInvoice(data: Record<string, unknown>): Promise<Record<string, unknown>>;
23
23
  updateInvoice(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
24
- deleteInvoice(id: string): Promise<void>;
24
+ /**
25
+ * Resolves to the soft-cancel payload when the backend answered 200 (non-draft
26
+ * invoice CANCELLED, not destroyed — VeriFactu hash chain), or void/undefined
27
+ * when it answered 204 (draft really removed). See `FrihetClient.deleteInvoice`.
28
+ */
29
+ deleteInvoice(id: string): Promise<Record<string, unknown> | void>;
25
30
  searchInvoices(query: string, params?: {
26
31
  limit?: number;
27
32
  offset?: number;
@@ -83,7 +88,8 @@ export interface IFrihetClient {
83
88
  getQuote(id: string): Promise<Record<string, unknown>>;
84
89
  createQuote(data: Record<string, unknown>): Promise<Record<string, unknown>>;
85
90
  updateQuote(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
86
- deleteQuote(id: string): Promise<void>;
91
+ /** Same 200-cancel / 204-destroy contract as {@link IFrihetClient.deleteInvoice}. */
92
+ deleteQuote(id: string): Promise<Record<string, unknown> | void>;
87
93
  listVendors(params?: {
88
94
  q?: string;
89
95
  limit?: number;
@@ -97,8 +103,8 @@ export interface IFrihetClient {
97
103
  deleteVendor(id: string): Promise<void>;
98
104
  sendInvoice(id: string, to?: string): Promise<Record<string, unknown>>;
99
105
  markInvoicePaid(id: string, paidDate?: string): Promise<Record<string, unknown>>;
100
- getInvoicePdf(id: string): Promise<Record<string, unknown>>;
101
- getInvoiceEInvoice(invoiceId: string): Promise<any>;
106
+ getInvoicePdf(id: string): Promise<import("./client.js").BinaryDocument>;
107
+ getInvoiceEInvoice(invoiceId: string): Promise<import("./client.js").EInvoiceDocument>;
102
108
  createCreditNote(invoiceId: string, data: {
103
109
  reason: string;
104
110
  reasonDescription?: string;
@@ -110,13 +116,15 @@ export interface IFrihetClient {
110
116
  daysOverdue?: number;
111
117
  }): Promise<any>;
112
118
  sendQuote(id: string, to?: string): Promise<Record<string, unknown>>;
113
- listWebhooks(params?: {
114
- limit?: number;
115
- offset?: number;
116
- }): Promise<PaginatedResponse<Record<string, unknown>>>;
117
- getWebhook(id: string): Promise<Record<string, unknown>>;
118
- createWebhook(data: Record<string, unknown>): Promise<Record<string, unknown>>;
119
- updateWebhook(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
119
+ listWebhooks(): Promise<{
120
+ data: Webhook[];
121
+ total: number;
122
+ _demo?: true;
123
+ _demoNotice?: string;
124
+ }>;
125
+ getWebhook(id: string): Promise<Webhook>;
126
+ createWebhook(data: CreateWebhookInput): Promise<CreateWebhookResult>;
127
+ updateWebhook(id: string, data: UpdateWebhookInput): Promise<Webhook>;
120
128
  deleteWebhook(id: string): Promise<void>;
121
129
  listClientContacts(clientId: string, params?: {
122
130
  limit?: number;
@@ -519,7 +527,7 @@ export interface IFrihetClient {
519
527
  eventType?: string;
520
528
  }): Promise<Record<string, unknown>>;
521
529
  exportPayroll(params: {
522
- format: "a3" | "contasol" | "sage" | "holded" | "siltra";
530
+ format: "a3" | "contasol" | "sage" | "siltra";
523
531
  month: string;
524
532
  }): Promise<Record<string, unknown>>;
525
533
  getPayrollChecklist(params: {
@@ -532,7 +540,7 @@ export interface IFrihetClient {
532
540
  getPermissionsMatrix(): Promise<Record<string, unknown>>;
533
541
  getMyPermissions(): Promise<Record<string, unknown>>;
534
542
  getCurrentPeriod(params?: {
535
- periodId?: string;
543
+ fiscalYear?: string;
536
544
  }): Promise<Record<string, unknown>>;
537
545
  closePeriod(data: {
538
546
  type: "monthly" | "quarterly";
package/dist/client.d.ts CHANGED
@@ -11,11 +11,63 @@
11
11
  * (`req.query.cursor`, functions/src/publicApi.ts in Frihet-ERP). `after`
12
12
  * is never sent on the wire. See src/__tests__/pagination-cursor-param.test.ts.
13
13
  */
14
- import type { PaginatedResponse } from "./types.js";
14
+ import type { CreateWebhookInput, CreateWebhookResult, PaginatedResponse, UpdateWebhookInput, Webhook } from "./types.js";
15
+ /**
16
+ * Hard size caps for document responses. Enforced TWICE: precheck on
17
+ * `Content-Length` (so an honest server doesn't waste bandwidth) and after
18
+ * streaming (so a missing/lying `Content-Length` still can't trigger an
19
+ * unbounded allocation).
20
+ *
21
+ * - PDF: 25 MiB — generous for any ERP-issued invoice PDF, including
22
+ * embedded logos, Facturae XML attachments, and stamp signatures.
23
+ * - XML: 5 MiB — UBL / Facturae / PEPPOL documents stay well under 1 MiB
24
+ * in practice; 5 MiB absorbs any historical / annex-laden outlier.
25
+ *
26
+ * Anything larger is rejected with `413 payload_too_large` BEFORE we allocate
27
+ * — the user sees a clean error, the worker doesn't OOM.
28
+ */
29
+ export declare const MAX_PDF_BYTES: number;
30
+ export declare const MAX_XML_BYTES: number;
31
+ /**
32
+ * Bounded binary document response. Always base64-encoded because MCP
33
+ * `structuredContent` is JSON-only — raw `Uint8Array` would coerce to a
34
+ * sparse object on the wire.
35
+ */
36
+ export interface BinaryDocument {
37
+ /** Echoed id from the request (invoice id), so callers can correlate. */
38
+ id: string;
39
+ /** Verbatim `Content-Type` from the response (e.g. `application/pdf`). */
40
+ contentType: string;
41
+ /** Byte length of the decoded body. Equal to the decoded base64 byte length. */
42
+ sizeBytes: number;
43
+ /** Base64-encoded bytes. */
44
+ base64: string;
45
+ /** Filename hint parsed from `Content-Disposition`, when present. */
46
+ filename?: string;
47
+ }
48
+ /**
49
+ * Bounded XML document response (UBL / CII / Facturae / PEPPOL / FatturaPA /
50
+ * XRechnung — anything declared `application/xml` or `text/xml`).
51
+ */
52
+ export interface XmlDocument {
53
+ /** Echoed id from the request (invoice id). */
54
+ id: string;
55
+ /** Strictly decoded UTF-8 XML text. */
56
+ xml: string;
57
+ /** Verbatim `Content-Type` from the response. */
58
+ contentType: string;
59
+ /** Byte length of the decoded UTF-8 body. */
60
+ sizeBytes: number;
61
+ /** Filename hint parsed from `Content-Disposition`, when present. */
62
+ filename?: string;
63
+ }
64
+ /** `/invoices/:id/xml` serves XML or a Factur-X PDF, depending on storage MIME. */
65
+ export type EInvoiceDocument = XmlDocument | BinaryDocument;
15
66
  export declare class FrihetApiError extends Error {
16
67
  readonly statusCode: number;
17
68
  readonly errorCode: string;
18
- constructor(statusCode: number, errorCode: string, message?: string);
69
+ readonly detail?: string | undefined;
70
+ constructor(statusCode: number, errorCode: string, message?: string, detail?: string | undefined);
19
71
  }
20
72
  export interface FrihetClientOptions {
21
73
  /**
@@ -23,13 +75,32 @@ export interface FrihetClientOptions {
23
75
  * Cloudflare Workers should pass ≤25000 to leave margin under the ~30s limit.
24
76
  */
25
77
  timeoutMs?: number;
78
+ /**
79
+ * Internal second factor for API keys provisioned by Frihet OAuth Workers.
80
+ * Ordinary dashboard/API keys must omit it. The value is sent only to the
81
+ * already-normalized API origin and is never included in logs or errors.
82
+ */
83
+ oauthServiceSecret?: string;
26
84
  }
27
85
  export declare class FrihetClient {
28
86
  private readonly apiKey;
29
87
  private readonly baseUrl;
30
88
  private readonly timeoutMs;
89
+ private readonly oauthServiceSecret?;
31
90
  constructor(apiKey: string, baseUrl?: string, options?: FrihetClientOptions);
32
91
  private request;
92
+ /** Fetch a raw response. The caller owns timeout and body consumption. */
93
+ private fetchRaw;
94
+ /** Consume one response body without ever retaining more than `maxBytes`. */
95
+ private readBoundedBody;
96
+ /**
97
+ * Bounded document fetch — ONE call, ONE Response, dispatch on Content-Type.
98
+ *
99
+ * Success and error bodies use the same bounded reader. The abort timer stays
100
+ * active until the body is complete, and a normal success performs one GET.
101
+ * A 429 may retry, matching the generic JSON client path.
102
+ */
103
+ private requestDocument;
33
104
  private sleep;
34
105
  /**
35
106
  * Wrapper for endpoints whose backend wraps the payload in a `{ data, meta }`
@@ -46,10 +117,15 @@ export declare class FrihetClient {
46
117
  * output schema (id/clientName/items/success all read as `undefined`). This
47
118
  * unwraps to `body.data`. Every create/update/action mutation routes through
48
119
  * here for exactly this reason; single-object reads (`getInvoice`/…) have since
49
- * #64. `deleteX` methods deliberately keep `request` (they return `void` — the
50
- * tool discards the body and synthesizes `{ success, id }`, so there is nothing
51
- * to unwrap). Only unwraps a non-array-object `data` (see guard below), so an
120
+ * #64. Most `deleteX` methods keep `request` (they return `void` — the tool
121
+ * discards the body and synthesizes `{ success, id }`, so there is nothing to
122
+ * unwrap). `deleteInvoice`/`deleteQuote` are the EXCEPTION and route through
123
+ * here: their backend answers 204 for a real delete but 200 + `{ data, meta }`
124
+ * when it soft-CANCELS a non-draft document instead (see those methods).
125
+ * Only unwraps a non-array-object `data` (see guard below), so an
52
126
  * array-`data` list envelope and any non-enveloped body pass through unchanged.
127
+ * A 204 yields `undefined` from `request`, which falls through untouched — that
128
+ * `undefined` is what tells the delete tools "destroyed, not cancelled".
53
129
  *
54
130
  * Only unwraps when the body is an object carrying a `data` property that is
55
131
  * itself a (non-array) object — i.e. a genuine single-object envelope. It is
@@ -75,7 +151,21 @@ export declare class FrihetClient {
75
151
  getInvoice(id: string): Promise<Record<string, unknown>>;
76
152
  createInvoice(data: Record<string, unknown>): Promise<Record<string, unknown>>;
77
153
  updateInvoice(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
78
- deleteInvoice(id: string): Promise<void>;
154
+ /**
155
+ * DELETE /invoices/{id} — NOT an unconditional destroy.
156
+ *
157
+ * The backend refuses to destroy a non-draft invoice (VeriFactu hash-chain
158
+ * integrity) and soft-CANCELS it instead, and it distinguishes the two
159
+ * outcomes on the wire (`publicApi.ts` DELETE branch):
160
+ * - 204 No Content → the draft row was really removed
161
+ * - 200 + `{ data: { id, status: 'cancelled', previousStatus,
162
+ * cancelledVia }, meta }` → the document still exists, now cancelled
163
+ *
164
+ * This used to be typed `Promise<void>` and the body was thrown away, so the
165
+ * tool reported "deleted successfully" for a document that was still there.
166
+ * Resolves to the unwrapped soft-cancel payload on 200, `undefined` on 204.
167
+ */
168
+ deleteInvoice(id: string): Promise<Record<string, unknown> | undefined>;
79
169
  searchInvoices(query: string, params?: {
80
170
  limit?: number;
81
171
  offset?: number;
@@ -137,7 +227,13 @@ export declare class FrihetClient {
137
227
  getQuote(id: string): Promise<Record<string, unknown>>;
138
228
  createQuote(data: Record<string, unknown>): Promise<Record<string, unknown>>;
139
229
  updateQuote(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
140
- deleteQuote(id: string): Promise<void>;
230
+ /**
231
+ * DELETE /quotes/{id} — same two-outcome contract as {@link deleteInvoice}:
232
+ * the backend soft-CANCELS a non-draft quote (200 + body), destroys only a
233
+ * clean draft with no delivery/response/attachment/conversion evidence (204),
234
+ * and refuses a protected draft with 409.
235
+ */
236
+ deleteQuote(id: string): Promise<Record<string, unknown> | undefined>;
141
237
  listVendors(params?: {
142
238
  q?: string;
143
239
  limit?: number;
@@ -151,8 +247,8 @@ export declare class FrihetClient {
151
247
  deleteVendor(id: string): Promise<void>;
152
248
  sendInvoice(id: string, to?: string): Promise<Record<string, unknown>>;
153
249
  markInvoicePaid(id: string, paidDate?: string): Promise<Record<string, unknown>>;
154
- getInvoicePdf(id: string): Promise<Record<string, unknown>>;
155
- getInvoiceEInvoice(invoiceId: string): Promise<any>;
250
+ getInvoicePdf(id: string): Promise<BinaryDocument>;
251
+ getInvoiceEInvoice(invoiceId: string): Promise<EInvoiceDocument>;
156
252
  /**
157
253
  * `POST /v1/invoices/:id/credit-note` — creates a rectificativa DRAFT.
158
254
  *
@@ -176,13 +272,13 @@ export declare class FrihetClient {
176
272
  daysOverdue?: number;
177
273
  }): Promise<any>;
178
274
  sendQuote(id: string, to?: string): Promise<Record<string, unknown>>;
179
- listWebhooks(params?: {
180
- limit?: number;
181
- offset?: number;
182
- }): Promise<PaginatedResponse<Record<string, unknown>>>;
183
- getWebhook(id: string): Promise<Record<string, unknown>>;
184
- createWebhook(data: Record<string, unknown>): Promise<Record<string, unknown>>;
185
- updateWebhook(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
275
+ listWebhooks(): Promise<{
276
+ data: Webhook[];
277
+ total: number;
278
+ }>;
279
+ getWebhook(id: string): Promise<Webhook>;
280
+ createWebhook(data: CreateWebhookInput): Promise<CreateWebhookResult>;
281
+ updateWebhook(id: string, data: UpdateWebhookInput): Promise<Webhook>;
186
282
  deleteWebhook(id: string): Promise<void>;
187
283
  listClientContacts(clientId: string, params?: {
188
284
  limit?: number;
@@ -585,7 +681,7 @@ export declare class FrihetClient {
585
681
  eventType?: string;
586
682
  }): Promise<Record<string, unknown>>;
587
683
  exportPayroll(params: {
588
- format: "a3" | "contasol" | "sage" | "holded" | "siltra";
684
+ format: "a3" | "contasol" | "sage" | "siltra";
589
685
  month: string;
590
686
  }): Promise<Record<string, unknown>>;
591
687
  getPayrollChecklist(params: {
@@ -598,7 +694,7 @@ export declare class FrihetClient {
598
694
  getPermissionsMatrix(): Promise<Record<string, unknown>>;
599
695
  getMyPermissions(): Promise<Record<string, unknown>>;
600
696
  getCurrentPeriod(params?: {
601
- periodId?: string;
697
+ fiscalYear?: string;
602
698
  }): Promise<Record<string, unknown>>;
603
699
  closePeriod(data: {
604
700
  type: "monthly" | "quarterly";