@hello-bill/node 1.0.0 → 1.0.1

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 (50) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +216 -0
  4. package/dist/express/index.d.cts +34 -0
  5. package/dist/express/index.d.ts +34 -0
  6. package/dist/express/index.js +1019 -0
  7. package/dist/express/index.js.map +1 -0
  8. package/dist/express/index.mjs +1016 -0
  9. package/dist/express/index.mjs.map +1 -0
  10. package/dist/hono/index.d.cts +37 -0
  11. package/dist/hono/index.d.ts +37 -0
  12. package/dist/hono/index.js +1036 -0
  13. package/dist/hono/index.js.map +1 -0
  14. package/dist/hono/index.mjs +1033 -0
  15. package/dist/hono/index.mjs.map +1 -0
  16. package/dist/index-BrfG82zs.d.cts +341 -0
  17. package/dist/index-EXetQn1f.d.ts +341 -0
  18. package/dist/index.d.cts +164 -0
  19. package/dist/index.d.ts +164 -0
  20. package/dist/index.js +1178 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/index.mjs +1164 -0
  23. package/dist/index.mjs.map +1 -0
  24. package/dist/koa/index.d.cts +42 -0
  25. package/dist/koa/index.d.ts +42 -0
  26. package/dist/koa/index.js +1042 -0
  27. package/dist/koa/index.js.map +1 -0
  28. package/dist/koa/index.mjs +1039 -0
  29. package/dist/koa/index.mjs.map +1 -0
  30. package/dist/next/index.d.cts +60 -0
  31. package/dist/next/index.d.ts +60 -0
  32. package/dist/next/index.js +1092 -0
  33. package/dist/next/index.js.map +1 -0
  34. package/dist/next/index.mjs +1090 -0
  35. package/dist/next/index.mjs.map +1 -0
  36. package/dist/types/index.d.cts +51 -0
  37. package/dist/types/index.d.ts +51 -0
  38. package/dist/types/index.js +12 -0
  39. package/dist/types/index.js.map +1 -0
  40. package/dist/types/index.mjs +9 -0
  41. package/dist/types/index.mjs.map +1 -0
  42. package/dist/webhook/index.d.cts +2 -0
  43. package/dist/webhook/index.d.ts +2 -0
  44. package/dist/webhook/index.js +342 -0
  45. package/dist/webhook/index.js.map +1 -0
  46. package/dist/webhook/index.mjs +336 -0
  47. package/dist/webhook/index.mjs.map +1 -0
  48. package/dist/webhooks-DPpqf7d2.d.cts +751 -0
  49. package/dist/webhooks-DPpqf7d2.d.ts +751 -0
  50. package/package.json +164 -7
@@ -0,0 +1,751 @@
1
+ /**
2
+ * Common HelloBill Partner API types.
3
+ * Source: HelloBill Partner API v15 (Revision 1.15, api_version "2026-05-10").
4
+ */
5
+ type Environment = 'sandbox' | 'production';
6
+ /**
7
+ * Determined by client_id prefix on the partner credential:
8
+ * `sb_*` → sandbox, `live_*` → live. Strict realm isolation; cross-realm IDs return 404.
9
+ */
10
+ type ClientMode = 'sandbox' | 'live';
11
+ type ApiVersion = '2026-05-10' | (string & {});
12
+ type RequestId = string;
13
+ /** UUID v4. Generated by SDK for idempotent POSTs; overridable per call. */
14
+ type IdempotencyKey = string;
15
+ /** ISO 8601 UTC timestamp, e.g. "2026-05-15T14:32:00Z". */
16
+ type IsoDateTime = string;
17
+ /** ISO 8601 date, e.g. "2026-06-01". */
18
+ type IsoDate = string;
19
+ /** UK postcode in canonical form, e.g. "SW1A 1AA". */
20
+ type Postcode = string;
21
+ type Email = string;
22
+ /** E.164 phone number, e.g. "+447700900123". */
23
+ type PhoneNumber = string;
24
+ type Url = string;
25
+ /**
26
+ * Cursor-paginated response envelope used by list endpoints (e.g. /products).
27
+ * `next_cursor` is `null` when no more pages.
28
+ */
29
+ interface Paginated<T> {
30
+ data: T[];
31
+ next_cursor: string | null;
32
+ has_more: boolean;
33
+ }
34
+ /**
35
+ * The product category an item belongs to.
36
+ * Note: the embed renders AnyVan as an internal UI line item; it does NOT
37
+ * correspond to a Partner API category.
38
+ */
39
+ type ProductCategory = 'energy' | 'water' | 'broadband' | 'council_tax' | 'mobile' | 'tv_licence' | 'home_insurance' | 'breakdown_cover';
40
+ /**
41
+ * "current" = the property the customer is moving into.
42
+ * "previous" = the property they are moving out of (only present when move.out supplied).
43
+ */
44
+ type PropertyLocation = 'current' | 'previous';
45
+ /** Shared base address shape used anywhere an address appears. */
46
+ interface AddressObject {
47
+ address_line_1: string;
48
+ address_line_2?: string;
49
+ address_line_3?: string;
50
+ city: string;
51
+ postcode: Postcode;
52
+ uprn?: string;
53
+ country?: 'GB';
54
+ }
55
+ /** @deprecated Use AddressObject. Kept for backwards-compatible imports. */
56
+ type Address = AddressObject;
57
+ type PropertyType = 'detached' | 'semi_detached' | 'terraced' | 'flat' | 'bungalow' | 'maisonette' | 'other';
58
+ type HeatingType = 'gas_central' | 'electric' | 'oil' | 'heat_pump' | 'district' | 'solid_fuel' | 'other';
59
+ type EpcRating = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';
60
+ /** A property where utilities are served. */
61
+ interface PropertyObject extends AddressObject {
62
+ bedrooms?: number;
63
+ number_occupants?: number;
64
+ property_type?: PropertyType;
65
+ epc_rating?: EpcRating;
66
+ heating_type?: HeatingType;
67
+ }
68
+ type CustomerType = 'owner_occupier' | 'tenant' | 'licensee' | 'lodger' | 'management_company' | 'other';
69
+ type OccupantType = 'owner' | 'tenant' | 'licensee' | 'lodger' | 'guarantor' | 'other';
70
+ interface Customer {
71
+ type: CustomerType;
72
+ email: Email;
73
+ first_name?: string;
74
+ last_name?: string;
75
+ phone?: PhoneNumber;
76
+ /** YYYY-MM-DD. Required at POST /customers. Never log. */
77
+ date_of_birth?: IsoDate;
78
+ is_primary_occupant?: boolean;
79
+ on_psr?: boolean;
80
+ /** Optional B2B billing/contact address; server defaults to addresses.current. */
81
+ address?: AddressObject;
82
+ }
83
+ /** @deprecated Use Customer. */
84
+ type Person = Customer;
85
+ interface OccupantObject {
86
+ type: OccupantType;
87
+ first_name?: string;
88
+ last_name?: string;
89
+ email?: Email;
90
+ phone?: PhoneNumber;
91
+ is_primary_occupant?: boolean;
92
+ }
93
+ type BillType = 'single' | 'split';
94
+ /** Customer-supplied move-in details for the new property. */
95
+ interface MoveIn {
96
+ move_in_date: IsoDate;
97
+ bill_type?: BillType;
98
+ rent_pcm?: number;
99
+ tenancy_start_date?: IsoDate;
100
+ tenancy_end_date?: IsoDate;
101
+ }
102
+ type MoveOutReason = 'end_of_tenancy' | 'sale' | 'transfer' | 'bereavement' | 'other';
103
+ interface MoveOutNotifyFlags {
104
+ energy?: boolean;
105
+ water?: boolean;
106
+ council_tax?: boolean;
107
+ tv_licence?: boolean;
108
+ broadband?: boolean;
109
+ mobile?: boolean;
110
+ }
111
+ type FuelType = 'electricity' | 'gas';
112
+ interface FinalRead {
113
+ category: 'energy' | 'water';
114
+ fuel?: FuelType;
115
+ meter_serial_number?: string;
116
+ reading: number;
117
+ read_date: IsoDate;
118
+ photo_data_base64?: string;
119
+ estimated?: boolean;
120
+ }
121
+ /**
122
+ * Customer-supplied move-out details for the previous property.
123
+ * When this is absent, the simple move-in flow runs (no van service,
124
+ * no comparison screen, no /products?include_previous call).
125
+ */
126
+ interface MoveOut {
127
+ move_out_date: IsoDate;
128
+ tenancy_end_date?: IsoDate;
129
+ reason?: MoveOutReason;
130
+ forwarding_address?: AddressObject;
131
+ notify?: MoveOutNotifyFlags;
132
+ final_reads?: FinalRead[];
133
+ }
134
+ interface Meters {
135
+ mpan?: string;
136
+ mprn?: string;
137
+ current_supplier_electricity?: string;
138
+ current_supplier_gas?: string;
139
+ }
140
+ interface Consent {
141
+ /** Must be `true` at POST /sessions. Spec-required. */
142
+ data_sharing_accepted: true;
143
+ data_sharing_accepted_at: IsoDateTime;
144
+ /** Customer-supplied. Drives marketing webhooks/email. */
145
+ marketing_accepted?: boolean;
146
+ }
147
+ type ContextEntry = {
148
+ name: string;
149
+ type: 'string';
150
+ value: string;
151
+ } | {
152
+ name: string;
153
+ type: 'number';
154
+ value: number;
155
+ } | {
156
+ name: string;
157
+ type: 'boolean';
158
+ value: boolean;
159
+ };
160
+ /**
161
+ * Per-field discovery / completeness signal returned alongside SessionResponse.
162
+ * Uses dot-notation: e.g. "addresses.current.postcode", "move.in.move_in_date".
163
+ */
164
+ type ProvidedFieldPath = string;
165
+ /** Async discovery state for a session. Embed polls /products until complete. */
166
+ type DiscoveryStatus = 'running' | 'complete';
167
+ /** Cache state on /products responses. */
168
+ type CacheStatus = 'fresh' | 'cached' | 'throttled' | 'partial';
169
+
170
+ declare const maskedSortCodeBrand: unique symbol;
171
+ type MaskedSortCode = string & {
172
+ readonly [maskedSortCodeBrand]: 'NN-**-NN';
173
+ };
174
+ type BankDetailsStatus = 'collected' | 'pending' | 'failed';
175
+ interface BankDetailsConsent {
176
+ direct_debit_authorised: true;
177
+ authorised_at: IsoDateTime;
178
+ consent_text_version: string;
179
+ }
180
+ interface BankDetailsRequest {
181
+ sort_code: string;
182
+ account_number: string;
183
+ account_holder_name: string;
184
+ consent: BankDetailsConsent;
185
+ }
186
+ interface StoredBankDetails {
187
+ id: string;
188
+ sort_code: string;
189
+ account_number: string;
190
+ account_holder_name: string;
191
+ consent: BankDetailsConsent;
192
+ modulus_check_passed: boolean;
193
+ status: BankDetailsStatus;
194
+ collected_at: IsoDateTime;
195
+ }
196
+ interface BankDetailsObject {
197
+ id: string;
198
+ account_number_ending: string;
199
+ sort_code_masked: MaskedSortCode;
200
+ bank_name?: string;
201
+ account_holder_name: string;
202
+ modulus_check_passed: boolean;
203
+ status: BankDetailsStatus;
204
+ collected_at: IsoDateTime;
205
+ consent: BankDetailsConsent;
206
+ }
207
+ interface BankDetailsResponse {
208
+ session_id: string;
209
+ bank_details: BankDetailsObject;
210
+ expires_at: IsoDateTime;
211
+ }
212
+
213
+ /**
214
+ * Session lifecycle types.
215
+ * POST /partner/sessions creates a session and returns a short-lived session_token
216
+ * that the embed presents to the frontend-facing endpoints.
217
+ */
218
+
219
+ interface AddressesObject {
220
+ /** The property the customer is moving INTO. Where utilities are set up. */
221
+ current: PropertyObject;
222
+ /** The property the customer is moving OUT of, if applicable. */
223
+ previous?: PropertyObject;
224
+ }
225
+ interface MoveObject {
226
+ in: MoveIn;
227
+ out?: MoveOut;
228
+ }
229
+ /**
230
+ * Canonical snake_case shape the SDK sends to POST /partner/sessions.
231
+ * `buildSessionPayload(req)` on the server is responsible for assembling this
232
+ * from whatever shape the partner's host page provides.
233
+ */
234
+ interface SessionPayload {
235
+ /** Partner-controlled correlation. Echoed back on webhooks as `data.referral_id`. */
236
+ referral_id?: string;
237
+ customer: Customer;
238
+ occupants?: OccupantObject[];
239
+ addresses: AddressesObject;
240
+ move: MoveObject;
241
+ meters?: Meters;
242
+ consent: Consent;
243
+ context?: ContextEntry[];
244
+ }
245
+ /** Response from POST /partner/sessions. */
246
+ interface SessionResponse {
247
+ session_id: string;
248
+ /** Short-lived JWT (300s) the embed presents to the SDK-mounted endpoints. */
249
+ session_token: string;
250
+ embed_url: string;
251
+ /** Initial discovery status. Embed polls /products until complete. */
252
+ discovery_status: 'running' | 'complete';
253
+ estimated_ready_seconds?: number;
254
+ /** Dot-notation paths of fields HelloBill received. Drives adaptive UI. */
255
+ provided_fields: ProvidedFieldPath[];
256
+ /** ISO 8601. Session resource expires after 90 days. */
257
+ expires_at: IsoDateTime;
258
+ }
259
+ /**
260
+ * Response from GET /partner/sessions/:id.
261
+ *
262
+ * Per spec §3.7, the endpoint echoes back the full SessionPayload the partner
263
+ * originally sent to POST /partner/sessions, extended with server-assigned fields.
264
+ * This is distinct from SessionResponse (POST projection) which carries session_token
265
+ * and embed_url but not the original payload fields.
266
+ */
267
+ interface SessionGetResponse extends SessionPayload {
268
+ /** Stable server-assigned session identifier. */
269
+ session_id: string;
270
+ /** ISO 8601 timestamp when the session was created. */
271
+ created_at: IsoDateTime;
272
+ /** ISO 8601 session expiry (90 days from creation). */
273
+ expires_at: IsoDateTime;
274
+ /** Lifecycle status of the session. */
275
+ status: 'created' | 'customer_created' | 'expired';
276
+ }
277
+ /**
278
+ * Idempotency window for POST /sessions: 24h on
279
+ * email + addresses.current.postcode + addresses.current.address_line_1.
280
+ * `move.out` is NOT part of the key — partners cannot retroactively change shape.
281
+ */
282
+ declare const SESSION_IDEMPOTENCY_TTL_MS: number;
283
+
284
+ /**
285
+ * Product catalog and property-detail types.
286
+ * Source: HelloBill Partner API v15 §§4, 5, 16.
287
+ */
288
+
289
+ type ProductId = string;
290
+ type LocationRole = PropertyLocation;
291
+ type ProductConfidence = 'high' | 'medium' | 'low';
292
+ interface ProductBase {
293
+ product_id: ProductId;
294
+ category: ProductCategory;
295
+ sub_categories: string[];
296
+ supplier_name: string;
297
+ supplier_logo_url?: string | null;
298
+ location: LocationRole;
299
+ /** True if this is the customer's existing supplier at the property. */
300
+ is_incumbent: boolean;
301
+ monthly_amount_pence?: number;
302
+ annual_amount_pence?: number;
303
+ confidence: ProductConfidence;
304
+ tariff_name?: string | null;
305
+ note?: string;
306
+ extracted_at: IsoDateTime;
307
+ /** Validity window for selecting this product (24h). */
308
+ expires_at: IsoDateTime;
309
+ }
310
+ interface EnergyProduct extends ProductBase {
311
+ category: 'energy';
312
+ details: {
313
+ unit_rate_pence_per_kwh: number;
314
+ unit_rate_pence_per_kwh_gas?: number;
315
+ standing_charge_pence_per_day: number;
316
+ standing_charge_pence_per_day_gas?: number;
317
+ estimated_annual_usage_kwh?: number;
318
+ estimated_annual_usage_kwh_gas?: number;
319
+ fuel_types: ('electricity' | 'gas')[];
320
+ contract_length_months?: number;
321
+ exit_fee_pence?: number;
322
+ green_percentage?: number;
323
+ tariff_type?: 'fixed' | 'variable' | 'tracker' | 'time_of_use' | 'prepayment' | 'ev';
324
+ smart_meter_required?: boolean;
325
+ };
326
+ }
327
+ interface WaterProduct extends ProductBase {
328
+ category: 'water';
329
+ details: {
330
+ metered: boolean;
331
+ estimated_litres_per_day?: number;
332
+ unit_rate_gbp_per_m3?: number;
333
+ standing_charge_gbp_per_year: number;
334
+ rateable_value_pence?: number;
335
+ sewerage_annual_pence?: number;
336
+ surface_water_drainage_pence?: number;
337
+ effective_tariff_year: string;
338
+ region: string;
339
+ };
340
+ }
341
+ interface BroadbandProduct extends ProductBase {
342
+ category: 'broadband';
343
+ details: {
344
+ download_speed_mbps: number;
345
+ upload_speed_mbps: number;
346
+ contract_length_months: number;
347
+ setup_fee_pence: number;
348
+ includes_line_rental: boolean;
349
+ technology: 'adsl' | 'fttc' | 'ftth' | 'full_fibre' | 'fixed_wireless' | 'mobile_5g_home' | 'satellite';
350
+ data_cap_gb?: number | null;
351
+ in_contract_discount_months?: number;
352
+ after_offer_monthly_pence?: number;
353
+ };
354
+ }
355
+ interface CouncilTaxProduct extends ProductBase {
356
+ category: 'council_tax';
357
+ details: {
358
+ local_authority: string;
359
+ region: 'england' | 'wales' | 'scotland';
360
+ band: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I';
361
+ annual_charge_pence: number;
362
+ instalment_options?: (10 | 12)[];
363
+ single_occupant_discount_eligible?: boolean;
364
+ student_exemption_eligible?: boolean;
365
+ };
366
+ }
367
+ interface MobileProduct extends ProductBase {
368
+ category: 'mobile';
369
+ details: {
370
+ sub_type: 'sim_only' | 'pay_monthly_handset' | 'pay_as_you_go' | 'data_only';
371
+ data_allowance_gb: number | 'unlimited';
372
+ minutes: number | 'unlimited';
373
+ texts: number | 'unlimited';
374
+ network_name: string;
375
+ network_type: 'mno' | 'mvno';
376
+ contract_length_months: number;
377
+ handset_included?: boolean;
378
+ handset_model?: string | null;
379
+ five_g_included?: boolean;
380
+ eu_roaming_included?: boolean;
381
+ };
382
+ }
383
+ interface TvLicenceProduct extends ProductBase {
384
+ category: 'tv_licence';
385
+ details: {
386
+ licence_type: 'standard' | 'concessionary_blind' | 'over_75_pension_credit' | 'nursing_home';
387
+ annual_charge_pence: number;
388
+ payment_frequency_options: ('annual' | 'quarterly' | 'monthly')[];
389
+ };
390
+ }
391
+ interface HomeInsuranceProduct extends ProductBase {
392
+ category: 'home_insurance';
393
+ details: {
394
+ cover_type: 'buildings' | 'contents' | 'buildings_and_contents' | 'landlord';
395
+ contract_length_months: number;
396
+ voluntary_excess_pence?: number;
397
+ compulsory_excess_pence: number;
398
+ accidental_damage_included?: boolean;
399
+ legal_cover_included?: boolean;
400
+ home_emergency_included?: boolean;
401
+ };
402
+ }
403
+ interface BreakdownCoverProduct extends ProductBase {
404
+ category: 'breakdown_cover';
405
+ details: {
406
+ sub_type: 'boiler' | 'home_emergency' | 'vehicle';
407
+ contract_length_months: number;
408
+ callout_limit_per_year?: number | 'unlimited';
409
+ parts_included?: boolean;
410
+ labour_included?: boolean;
411
+ };
412
+ }
413
+ type Product = EnergyProduct | WaterProduct | BroadbandProduct | CouncilTaxProduct | MobileProduct | TvLicenceProduct | HomeInsuranceProduct | BreakdownCoverProduct;
414
+ /**
415
+ * Response from GET /partner/sessions/:id/products.
416
+ * Partial results are valid: `products_complete: false` means discovery is still
417
+ * running and the embed should poll.
418
+ */
419
+ interface ProductsResponse {
420
+ session_id: string;
421
+ products: Product[];
422
+ products_complete: boolean;
423
+ cache_status: CacheStatus;
424
+ total_suppliers_queried: number;
425
+ total_suppliers_responded: number;
426
+ next_cursor: string | null;
427
+ }
428
+ /** Query options for GET /products. */
429
+ interface ProductsQuery {
430
+ cursor?: string;
431
+ limit?: number;
432
+ categories?: ProductCategory[];
433
+ include_previous?: boolean;
434
+ force_refresh?: boolean;
435
+ }
436
+ type DataCompleteness = 'complete' | 'partial' | 'empty';
437
+ type PropertyTypeCode = 'house' | 'flat' | 'maisonette' | 'bungalow' | 'other';
438
+ type PropertyTypeSubcode = 'detached' | 'semi_detached' | 'terraced' | 'end_terrace' | 'purpose_built_flat' | 'converted_flat' | 'studio' | 'penthouse' | 'other';
439
+ type BuildingType = 'residential' | 'mixed_use' | 'commercial' | 'unknown';
440
+ type BuildingStyle = 'victorian' | 'edwardian' | 'georgian' | 'interwar' | 'post_war' | 'modern' | 'new_build' | 'period' | 'unknown';
441
+ interface NearbyPoi {
442
+ poi_id: string;
443
+ name: string;
444
+ sub: string;
445
+ distance_minutes: number;
446
+ mode: 'walk' | 'drive' | 'cycle' | 'tube';
447
+ icon_token: string;
448
+ }
449
+ type CollectionDay = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
450
+ type BinType = 'general_waste' | 'recycling' | 'food_waste' | 'garden_waste' | 'glass';
451
+ type CollectionFrequency = 'weekly' | 'fortnightly' | 'monthly';
452
+ interface BinCollectionPayload {
453
+ primary_collection_day: CollectionDay;
454
+ primary_collection_types: BinType[];
455
+ collection_frequency: CollectionFrequency;
456
+ /** ISO 'YYYY-MM-DD' or '' if not derivable from move_in_date */
457
+ next_collection_date: string;
458
+ days_after_move_in: number;
459
+ /** e.g. 'Tuesday evening' or '' */
460
+ reminder_eve_label: string;
461
+ secondary_collection_day?: CollectionDay;
462
+ secondary_collection_types?: BinType[];
463
+ }
464
+ interface PropertyDetailObject {
465
+ property_type_code?: PropertyTypeCode;
466
+ property_type_subcode?: PropertyTypeSubcode;
467
+ building_type?: BuildingType;
468
+ building_style?: BuildingStyle;
469
+ number_of_bedrooms?: number;
470
+ number_of_bathrooms?: number;
471
+ floor_number?: number;
472
+ energy_performance_rating?: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';
473
+ has_parking?: boolean;
474
+ property_has_off_road_parking?: boolean;
475
+ property_has_underground_parking?: boolean;
476
+ property_has_a_driveway?: boolean;
477
+ nearby?: NearbyPoi[];
478
+ bin_collection?: BinCollectionPayload;
479
+ }
480
+ interface PropertyDetailResponse {
481
+ session_id: string;
482
+ property: PropertyDetailObject;
483
+ data_completeness: DataCompleteness;
484
+ extracted_at: IsoDateTime;
485
+ expires_at: IsoDateTime;
486
+ }
487
+
488
+ /**
489
+ * Letter of Authority (LoA) types.
490
+ * GET /partner/sessions/:id/loa?product_ids=... returns one or more LoA templates
491
+ * covering the customer's product selection. URLs require Bearer auth and must
492
+ * be proxied server-side — never embedded in client HTML.
493
+ */
494
+
495
+ type LoaId = string;
496
+ interface LoaRecord {
497
+ loa_id: LoaId;
498
+ /**
499
+ * Bearer-gated URL to the PDF rendering of the LoA text.
500
+ * Never expose directly to the browser.
501
+ */
502
+ loa_text_url: Url;
503
+ /** Bearer-gated URL to an HTML rendering suitable for in-app display. */
504
+ loa_text_html_url: Url;
505
+ /** SHA-256 of the rendered LoA body — used to detect template changes mid-flow. */
506
+ checksum: string;
507
+ /** Product IDs whose switching this LoA authorises. */
508
+ covers_product_ids: ProductId[];
509
+ /** ISO 8601. Template expiry (~30 days). After this, fetch a fresh LoA. */
510
+ expires_at: IsoDateTime;
511
+ /** Template version, monotonically incremented on content change. */
512
+ version: 'loa-template-v4';
513
+ }
514
+ interface LoaListResponse {
515
+ session_id: string;
516
+ loas: LoaRecord[];
517
+ }
518
+ /**
519
+ * Signed LoA payload sent at POST /partner/sessions/:id/customers.
520
+ * `signed_checksum` MUST equal the LoA `checksum` at the time of signing —
521
+ * otherwise the server returns `loa.template_changed` and the embed must re-fetch + re-sign.
522
+ */
523
+ interface LoASignature {
524
+ loa_id: LoaId;
525
+ signed_checksum: string;
526
+ /** ISO 8601 — client-reported sign time. Server stamps `signed_at_server`. */
527
+ signed_at_client: IsoDateTime;
528
+ /** Name as typed/signed by the customer. */
529
+ signed_by_full_name: string;
530
+ signature_image: SignatureImage;
531
+ /** Customer IP observed by the partner at signing time. */
532
+ ip_address?: string;
533
+ /** Customer user-agent observed by the partner at signing time. */
534
+ user_agent?: string;
535
+ }
536
+ /**
537
+ * PNG signature only (no JPEG). Re-encoded server-side via libpng; SVG is parsed
538
+ * and stripped. Decoded size ≤200KB, dimensions ≤2000×2000px. Recommended
539
+ * canvas: 800×240px.
540
+ */
541
+ interface SignatureImage {
542
+ mime_type: 'image/png' | 'image/svg+xml';
543
+ /** Base64-encoded image bytes (no data: prefix). */
544
+ data_base64: string;
545
+ /** Optional capture width hint in pixels. */
546
+ width?: number;
547
+ /** Optional capture height hint in pixels. */
548
+ height?: number;
549
+ }
550
+
551
+ /**
552
+ * Customer creation and status types.
553
+ * POST /partner/sessions/:id/customers is idempotent — repeats return 200 with the
554
+ * existing customer_id, never 409.
555
+ */
556
+
557
+ interface SavingsSummaryResponse {
558
+ customer_id: string;
559
+ time_saved_minutes: number;
560
+ annual_savings_pence: number;
561
+ services_set_up: number;
562
+ extracted_at: IsoDateTime;
563
+ }
564
+
565
+ type CustomerId = string;
566
+
567
+ interface CreateCustomerInput {
568
+ selected_products: SelectedProduct[];
569
+ loa_signatures: LoASignature[];
570
+ }
571
+ interface SelectedProduct {
572
+ product_id: ProductId;
573
+ /** Shape varies by product category; current-address products only. */
574
+ onboarding_info?: OnboardingInfo;
575
+ }
576
+ interface OnboardingInfo {
577
+ meter_read_electricity?: number;
578
+ meter_read_gas?: number;
579
+ preferred_switch_date?: string;
580
+ preferred_install_date?: string;
581
+ existing_line_provider?: string;
582
+ keep_existing_number?: boolean;
583
+ moved_in_date?: string;
584
+ meter_read?: number;
585
+ notes?: string;
586
+ }
587
+ interface CustomerCreateResponse {
588
+ customer_id: CustomerId;
589
+ interstitial_url: string;
590
+ app_deeplinks: AppDeeplinks;
591
+ magic_link_sent: boolean;
592
+ product_setup: {
593
+ initiated: number;
594
+ products: ProductSetup[];
595
+ };
596
+ }
597
+ interface AppDeeplinks {
598
+ ios?: string;
599
+ android?: string;
600
+ universal: string;
601
+ }
602
+ /** Per-product setup state on creation. Status advances via setup_status webhooks. */
603
+ interface ProductSetup {
604
+ product_id: ProductId;
605
+ setup_id: string;
606
+ category: ProductCategory;
607
+ sub_categories: string[];
608
+ supplier_name: string;
609
+ status: SetupStatus;
610
+ location: PropertyLocation;
611
+ status_updated_at: IsoDateTime;
612
+ max_retries_exceeded: boolean;
613
+ details?: {
614
+ account_number?: string;
615
+ switch_date?: IsoDate;
616
+ direct_debit_setup?: boolean;
617
+ notified_at?: IsoDateTime;
618
+ channel?: 'email' | 'api' | 'postal';
619
+ final_reads_submitted?: boolean;
620
+ };
621
+ expected_resolution_at?: IsoDateTime;
622
+ }
623
+ /**
624
+ * Status values on /partner/customers/:id/status.
625
+ * - `pending` / `in_progress` / `failed` are universal
626
+ * - `confirmed` only applies to current-address setups
627
+ * - `notified` / `acknowledged` only apply to previous-address (move-out) setups
628
+ */
629
+ type SetupStatus = 'pending_setup' | 'in_progress' | 'awaiting_supplier' | 'confirmed' | 'notified' | 'acknowledged' | 'failed';
630
+ type OverallStatus = 'pending' | 'in_progress' | 'confirmed' | 'partial' | 'failed';
631
+ type MoveOutStatus = 'pending' | 'in_progress' | 'notified' | 'partial' | 'failed' | null;
632
+ type SubscriptionStatus = 'not_subscribed' | 'trialing' | 'active' | 'past_due' | 'cancelled';
633
+ /** GET /partner/customers/:id/status response. */
634
+ interface CustomerStatusResponse {
635
+ customer_id: CustomerId;
636
+ overall_status: OverallStatus;
637
+ /** Null when no move.out was supplied — the two state machines run independently. */
638
+ move_out_status: MoveOutStatus;
639
+ subscription_status: SubscriptionStatus;
640
+ trial_ends_at: IsoDateTime | null;
641
+ products: Array<ProductSetup & {
642
+ last_updated_at: IsoDateTime;
643
+ }>;
644
+ last_updated_at: IsoDateTime;
645
+ }
646
+
647
+ /**
648
+ * Webhook event types.
649
+ * Delivery is at-least-once and out-of-order. Always dedupe on `event.id`.
650
+ * HMAC signature header: `X-HelloBill-Signature: t=<unix>,v1=<hex>` — verifyWebhook() parses + validates.
651
+ */
652
+
653
+ type WebhookEventId = string;
654
+ interface WebhookEventBase<TType extends string, TData> {
655
+ id: WebhookEventId;
656
+ type: TType;
657
+ event: TType;
658
+ created_at: IsoDateTime;
659
+ api_version: ApiVersion;
660
+ environment: Environment;
661
+ livemode: boolean;
662
+ data: TData & {
663
+ session_id: string;
664
+ /** Partner-controlled correlation, echoed from SessionPayload.referral_id. null when absent. */
665
+ referral_id: string | null;
666
+ /** Echo of SessionPayload.context (or [] when absent). */
667
+ context: ContextEntry[];
668
+ };
669
+ }
670
+ type SessionEmbedOpened = WebhookEventBase<'session.embed_opened', Record<string, never>>;
671
+ type SessionDetailsConfirmed = WebhookEventBase<'session.details_confirmed', {
672
+ corrections: string[];
673
+ }>;
674
+ type SessionProductsSelected = WebhookEventBase<'session.products_selected', {
675
+ products: Array<{
676
+ product_id: ProductId;
677
+ category: ProductCategory;
678
+ sub_categories: string[];
679
+ location: PropertyLocation;
680
+ }>;
681
+ }>;
682
+ type SessionLoaSigned = WebhookEventBase<'session.loa_signed', {
683
+ loa_id: string;
684
+ /** Server-stamped at LoA signature time. Authoritative for LoA 12-month validity (see §17). Distinct from signed_at_client which is partner-supplied. */
685
+ signed_at_server: IsoDateTime;
686
+ signed_by_full_name: string;
687
+ }>;
688
+ type SessionAccountCreated = WebhookEventBase<'session.account_created', {
689
+ customer_id: CustomerId;
690
+ }>;
691
+ type SessionAppInstalled = WebhookEventBase<'session.app_installed', {
692
+ customer_id: CustomerId;
693
+ device_type: 'ios' | 'android';
694
+ installed_at: IsoDateTime;
695
+ }>;
696
+ type SessionAppDeferred = WebhookEventBase<'session.app_deferred', {
697
+ customer_id: CustomerId;
698
+ deferred_reason: string;
699
+ }>;
700
+ type SubscriptionChanged = WebhookEventBase<'subscription.changed', {
701
+ customer_id: CustomerId;
702
+ previous_status: SubscriptionStatus | null;
703
+ new_status: SubscriptionStatus;
704
+ trial_ends_at: IsoDateTime | null;
705
+ }>;
706
+ type SetupStatusChanged = WebhookEventBase<'setup_status.changed', {
707
+ customer_id: CustomerId;
708
+ product_id: ProductId;
709
+ setup_id: string;
710
+ category: ProductCategory;
711
+ sub_categories: string[];
712
+ supplier_name: string;
713
+ /** "current" = switching at new property; "previous" = closure flow at old property. */
714
+ location: PropertyLocation;
715
+ previous_status: SetupStatus;
716
+ new_status: SetupStatus;
717
+ max_retries_exceeded: boolean;
718
+ details?: ProductSetup['details'];
719
+ }>;
720
+ type MoveOutNotificationSent = WebhookEventBase<'move_out_notification.sent', {
721
+ customer_id: CustomerId;
722
+ setup_id: string;
723
+ product_id: ProductId;
724
+ category: ProductCategory;
725
+ supplier_name: string;
726
+ location: 'previous';
727
+ notified_at: IsoDateTime;
728
+ channel: 'email' | 'api' | 'postal';
729
+ }>;
730
+ type MoveOutNotificationFailed = WebhookEventBase<'move_out_notification.failed', {
731
+ customer_id: CustomerId;
732
+ setup_id: string;
733
+ product_id: ProductId;
734
+ category: ProductCategory;
735
+ supplier_name: string;
736
+ location: 'previous';
737
+ failed_at: IsoDateTime;
738
+ reason: string;
739
+ retry_scheduled_at?: IsoDateTime;
740
+ }>;
741
+ type BankDetailsCollected = WebhookEventBase<'bank_details.collected', {
742
+ bank_details_id: string;
743
+ account_number_ending: string;
744
+ sort_code_masked: MaskedSortCode;
745
+ modulus_check_passed: boolean;
746
+ collected_at: IsoDateTime;
747
+ }>;
748
+ type WebhookEvent = SessionEmbedOpened | SessionDetailsConfirmed | SessionProductsSelected | SessionLoaSigned | SessionAccountCreated | SessionAppInstalled | SessionAppDeferred | SubscriptionChanged | SetupStatusChanged | MoveOutNotificationSent | MoveOutNotificationFailed | BankDetailsCollected;
749
+ type WebhookEventType = WebhookEvent['event'];
750
+
751
+ export { type Meters as $, type Address as A, type BankDetailsRequest as B, type CreateCustomerInput as C, type Customer as D, type CustomerType as E, type DataCompleteness as F, type DiscoveryStatus as G, type Email as H, type EnergyProduct as I, type Environment as J, type EpcRating as K, type LoaListResponse as L, type FinalRead as M, type FuelType as N, type HeatingType as O, type ProductsQuery as P, type HomeInsuranceProduct as Q, type RequestId as R, type SessionPayload as S, type IdempotencyKey as T, type IsoDate as U, type IsoDateTime as V, type LoASignature as W, type LoaId as X, type LoaRecord as Y, type LocationRole as Z, type MaskedSortCode as _, type SessionResponse as a, type MobileProduct as a0, type MoveIn as a1, type MoveObject as a2, type MoveOut as a3, type MoveOutNotificationFailed as a4, type MoveOutNotificationSent as a5, type MoveOutNotifyFlags as a6, type MoveOutReason as a7, type MoveOutStatus as a8, type NearbyPoi as a9, type SessionAppInstalled as aA, type SessionDetailsConfirmed as aB, type SessionEmbedOpened as aC, type SessionLoaSigned as aD, type SessionProductsSelected as aE, type SetupStatus as aF, type SetupStatusChanged as aG, type SignatureImage as aH, type StoredBankDetails as aI, type SubscriptionChanged as aJ, type SubscriptionStatus as aK, type TvLicenceProduct as aL, type Url as aM, type WaterProduct as aN, type WebhookEvent as aO, type WebhookEventBase as aP, type WebhookEventId as aQ, type WebhookEventType as aR, type OccupantObject as aa, type OccupantType as ab, type OnboardingInfo as ac, type OverallStatus as ad, type Paginated as ae, type Person as af, type PhoneNumber as ag, type Postcode as ah, type Product as ai, type ProductBase as aj, type ProductCategory as ak, type ProductConfidence as al, type ProductId as am, type ProductSetup as an, type PropertyDetailObject as ao, type PropertyLocation as ap, type PropertyObject as aq, type PropertyType as ar, type PropertyTypeCode as as, type PropertyTypeSubcode as at, type ProvidedFieldPath as au, SESSION_IDEMPOTENCY_TTL_MS as av, type SavingsSummaryResponse as aw, type SelectedProduct as ax, type SessionAccountCreated as ay, type SessionAppDeferred as az, type SessionGetResponse as b, type ProductsResponse as c, type PropertyDetailResponse as d, type CustomerCreateResponse as e, type CustomerId as f, type CustomerStatusResponse as g, type BankDetailsResponse as h, type AddressObject as i, type AddressesObject as j, type ApiVersion as k, type AppDeeplinks as l, type BankDetailsCollected as m, type BankDetailsConsent as n, type BankDetailsObject as o, type BankDetailsStatus as p, type BillType as q, type BreakdownCoverProduct as r, type BroadbandProduct as s, type BuildingStyle as t, type BuildingType as u, type CacheStatus as v, type ClientMode as w, type Consent as x, type ContextEntry as y, type CouncilTaxProduct as z };