@isnap/sdk 1.3.0 → 1.4.0-next.204

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 (42) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/client.d.ts +2 -0
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +3 -0
  6. package/dist/client.js.map +1 -1
  7. package/dist/generated/openapi.d.ts +1784 -346
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/resources/apps.d.ts +11 -4
  13. package/dist/resources/apps.d.ts.map +1 -1
  14. package/dist/resources/apps.js +11 -4
  15. package/dist/resources/apps.js.map +1 -1
  16. package/dist/resources/lines.d.ts +207 -6
  17. package/dist/resources/lines.d.ts.map +1 -1
  18. package/dist/resources/lines.js +216 -6
  19. package/dist/resources/lines.js.map +1 -1
  20. package/dist/resources/macos-sessions.d.ts +48 -0
  21. package/dist/resources/macos-sessions.d.ts.map +1 -0
  22. package/dist/resources/macos-sessions.js +51 -0
  23. package/dist/resources/macos-sessions.js.map +1 -0
  24. package/dist/resources/preorders.d.ts +19 -7
  25. package/dist/resources/preorders.d.ts.map +1 -1
  26. package/dist/resources/preorders.js +19 -7
  27. package/dist/resources/preorders.js.map +1 -1
  28. package/dist/resources/webhooks.d.ts +1 -1
  29. package/dist/resources/webhooks.js +1 -1
  30. package/dist/version.d.ts +2 -2
  31. package/dist/version.d.ts.map +1 -1
  32. package/dist/version.js +13 -5
  33. package/dist/version.js.map +1 -1
  34. package/dist/webhooks/types.d.ts +46 -3
  35. package/dist/webhooks/types.d.ts.map +1 -1
  36. package/dist/webhooks/types.js +3 -0
  37. package/dist/webhooks/types.js.map +1 -1
  38. package/dist/webhooks/verify.d.ts +22 -9
  39. package/dist/webhooks/verify.d.ts.map +1 -1
  40. package/dist/webhooks/verify.js +101 -29
  41. package/dist/webhooks/verify.js.map +1 -1
  42. package/package.json +1 -1
@@ -387,7 +387,7 @@ export interface paths {
387
387
  };
388
388
  /**
389
389
  * List available lines (public marketplace)
390
- * @description Browse the marketplace inventory of unassigned lines. Filter by billing tier, area code, country code, status. Returns only `status: available` and never the iSnap-internal `shared_pool` tier. Unauthenticated — partners use this in prospecting / quote flows.
390
+ * @description Browse the marketplace inventory of unassigned lines. Filter by billing tier, area code, country code, state, country. Returns only `status: available` and never the iSnap-internal `shared_pool` tier — `?status=` therefore accepts `available` alone and refuses any other status with a 400, rather than answering with a list you did not ask for (WHA-2777). Unauthenticated — partners use this in prospecting / quote flows.
391
391
  */
392
392
  get: {
393
393
  parameters: {
@@ -399,6 +399,7 @@ export interface paths {
399
399
  state?: string;
400
400
  /** @description Country filter — ISO alpha-2 code or country name. */
401
401
  country?: string;
402
+ /** @description Comma-separated set of statuses to filter on. Every member must be one of: available — anything else is a 400, never an empty list. Omitted or blank means no status filter. */
402
403
  status?: string;
403
404
  page?: number;
404
405
  limit?: number;
@@ -551,7 +552,7 @@ export interface paths {
551
552
  };
552
553
  /**
553
554
  * Get line details (public marketplace)
554
- * @description Fetch a single line by its `line_<base62>` id. Available unauthenticated for the marketplace browsing flow; partner-private metadata still requires API key access via `GET /v1/lines/my`.
555
+ * @description Fetch a single line by its `line_<base62>` id. Available unauthenticated for the marketplace browsing flow, where the `phone_number` is masked so the inventory cannot be scraped. Authenticate with your API key (or dashboard JWT) and, when you own the line, the `phone_number` comes back unmasked — the same full number `GET /v1/lines/my` returns (WHA-3030). A credential that does not own the line, or none at all, still gets the masked number.
555
556
  */
556
557
  get: {
557
558
  parameters: {
@@ -623,8 +624,8 @@ export interface paths {
623
624
  options?: never;
624
625
  head?: never;
625
626
  /**
626
- * Update line metadata
627
- * @description Merge-patch the line's free-form `metadata` tag object. Pass `null` for a key to delete it; pass a new value to overwrite. Metadata is echoed on every `line.*` webhook so partners can attribute events back to their own customer IDs.
627
+ * Update a line
628
+ * @description Update the line's free-form `metadata` tag object, its billing anchor, or both. **`metadata`** is a MERGE-patch: pass `null` for a key to delete it, a new value to overwrite. It is echoed on every `line.*` webhook so partners can attribute events back to their own customer IDs. **`billing_anchor_at`** is the date the customer who pays for this line is billed on: a change takes effect at the NEXT occurrence and never rewrites a period already billed, and `null` clears it so the line falls back to its activation instant. Send at least one of the two — a body that sets neither is refused with `no_fields_to_patch`.
628
629
  */
629
630
  patch: {
630
631
  parameters: {
@@ -1059,7 +1060,7 @@ export interface paths {
1059
1060
  put?: never;
1060
1061
  /**
1061
1062
  * Activate a reserved line
1062
- * @description Commit a `reserved` line to `active`. Wholesale partners (no Stripe checkout surface) use this as the second phase of reserve→activate — they reserve, bill their own end-user, then activate within the 15-minute window with no per-line Stripe sub (EOM-metered), and may pick the INBOUND product via `inbound_only` (a line that cannot initiate — the contact must message first, after which the exchange is unrestricted; not receive-only). Direct callers get the test/dev mock-sub shortcut (production direct activation happens via the Stripe webhook on `checkout.session.completed`); `inbound_only` is ignored for them (theirs comes from the checkout price).
1063
+ * @description Commit a `reserved` line to `active`. **Wholesale partners only** (no Stripe checkout surface): this is the second phase of reserve→activate — they reserve, bill their own end-user, then activate within the 15-minute window with no per-line Stripe sub (EOM-metered), and may pick the INBOUND product via `inbound_only` (a line that cannot initiate — the contact must message first, after which the exchange is unrestricted; not receive-only). Pass `billing_anchor_at` to bill this line on the date your own customer is billed on rather than on its activation instant — one full month at each occurrence, with a credit at the first one for the days the activation month had already paid for past it. Direct customers get `402 checkout_required`: their line is activated by the `checkout.session.completed` webhook once `POST /v1/billing/checkout` is paid, which is what mints the Stripe subscription that bills them (§2.5 Flow A). `inbound_only` is meaningless for them — theirs comes from the checkout price.
1063
1064
  */
1064
1065
  post: {
1065
1066
  parameters: {
@@ -1109,6 +1110,15 @@ export interface paths {
1109
1110
  "application/json": components["schemas"]["ErrorEnvelope"];
1110
1111
  };
1111
1112
  };
1113
+ /** @description Payment required — no billing relationship entitles this call */
1114
+ 402: {
1115
+ headers: {
1116
+ [name: string]: unknown;
1117
+ };
1118
+ content: {
1119
+ "application/json": components["schemas"]["ErrorEnvelope"];
1120
+ };
1121
+ };
1112
1122
  /** @description Forbidden — caller authenticated but not allowed */
1113
1123
  403: {
1114
1124
  headers: {
@@ -1496,7 +1506,7 @@ export interface paths {
1496
1506
  patch?: never;
1497
1507
  trace?: never;
1498
1508
  };
1499
- "/v1/lines/{id}/release": {
1509
+ "/v1/lines/{id}/variant": {
1500
1510
  parameters: {
1501
1511
  query?: never;
1502
1512
  header?: never;
@@ -1506,8 +1516,8 @@ export interface paths {
1506
1516
  get?: never;
1507
1517
  put?: never;
1508
1518
  /**
1509
- * Release a line back to the marketplace
1510
- * @description Cancel the line's ownership and return it to `available`. Cancels the underlying Stripe subscription at period end. The caller must own the line. See `/concepts/billing` for the proration rules.
1519
+ * Switch a rental line between inbound-only and two-way
1520
+ * @description Move a rental line you own between the INBOUND product (`inbound_only: true` — the line may only message a handle that has messaged it first; it is NOT receive-only) and the TWO-WAY product, **keeping the same phone number, hardware and device session**. `effective` says WHEN: `now` (the default, and the only behaviour before the field existed) applies it to the very next send; `next_anchor` stores it against the line's next billing anchor, leaves `inbound_only` alone, and surfaces the change as `pending_variant` on the line — which is what a DOWNGRADE needs, so the customer keeps the product they have already paid for through the month. Idempotent on the value in both modes: asking for the variant the line already has (or re-scheduling the change already pending) returns 200 and writes nothing. **A scheduled call can therefore legitimately answer `pending_variant: null`** — scheduling the variant the line ALREADY carries writes nothing, because nothing is pending: the line will still be on that variant at the anchor. That is a truthful 200, not a failure. Tell it apart from a dropped-field failure by reading the top-level `inbound_only`: equal to what you asked for means already-at-target; different means your `effective` never reached this endpoint and the change was applied immediately. `not_before` (optional ISO instant, `next_anchor` only, default now) is the earliest instant the change may take effect; iSnap schedules it at the first anchor occurrence AT OR AFTER it, so a quarterly or annual customer keeps their product for the whole period they paid for instead of losing it at the next anchor. A `not_before` in the past is refused with `400 not_before_in_the_past` rather than clamped. Refused with 409 on a non-rental line (`not_a_rental_line`), on a line that is not `active` (`line_not_active`), on a line carrying an iSnap Stripe subscription (`variant_bound_to_stripe_price`) — there the subscription's price is the variant and changing the subscription is how it moves — and, when scheduling, on a line that already has a DIFFERENT change pending (`variant_change_already_pending`; cancel it first). Emits `line.variant_changed` on an immediate change and `line.variant_scheduled` on a scheduled one.
1511
1521
  */
1512
1522
  post: {
1513
1523
  parameters: {
@@ -1518,9 +1528,13 @@ export interface paths {
1518
1528
  };
1519
1529
  cookie?: never;
1520
1530
  };
1521
- requestBody?: never;
1531
+ requestBody?: {
1532
+ content: {
1533
+ "application/json": components["schemas"]["SetLineVariant"];
1534
+ };
1535
+ };
1522
1536
  responses: {
1523
- /** @description Line released */
1537
+ /** @description Line. With `effective: "now"` its `inbound_only` reflects the requested variant; with `effective: "next_anchor"` `inbound_only` is unchanged and `pending_variant` carries the requested variant and the date it applies. */
1524
1538
  200: {
1525
1539
  headers: {
1526
1540
  [name: string]: unknown;
@@ -1571,6 +1585,15 @@ export interface paths {
1571
1585
  "application/json": components["schemas"]["ErrorEnvelope"];
1572
1586
  };
1573
1587
  };
1588
+ /** @description Conflict — concurrent or terminal state */
1589
+ 409: {
1590
+ headers: {
1591
+ [name: string]: unknown;
1592
+ };
1593
+ content: {
1594
+ "application/json": components["schemas"]["ErrorEnvelope"];
1595
+ };
1596
+ };
1574
1597
  /** @description Too many requests — rate limit exceeded */
1575
1598
  429: {
1576
1599
  headers: {
@@ -1591,24 +1614,11 @@ export interface paths {
1591
1614
  };
1592
1615
  };
1593
1616
  };
1594
- delete?: never;
1595
- options?: never;
1596
- head?: never;
1597
- patch?: never;
1598
- trace?: never;
1599
- };
1600
- "/v1/lines/{id}/quota": {
1601
- parameters: {
1602
- query?: never;
1603
- header?: never;
1604
- path?: never;
1605
- cookie?: never;
1606
- };
1607
1617
  /**
1608
- * Get a line's quota snapshot
1609
- * @description Five-bucket quota snapshot: new-contacts, known-contacts, total daily messages, hourly, and minute counters with their effective limits plus 80%-warning flags. Drives the dashboard usage panel and partner self-service throttle decisions.
1618
+ * Cancel a scheduled variant change
1619
+ * @description Call off the variant change scheduled for this line's next billing anchor (`POST /v1/lines/{id}/variant` with `effective: 'next_anchor'`). The line's live `inbound_only` is never touched by this call — it was never moved by the scheduling either — so the only effect is that `pending_variant` becomes `null` and nothing happens at the anchor. Answers `404 no_pending_variant_change` when the line carries no pending change, which includes a change the anchor has already applied; a change applied while this request was in flight is `409 variant_change_already_applied`. Succeeds on a suspended or cancelled line: withdrawing an instruction must never be blocked by the state that made it moot. Emits `line.variant_unscheduled`.
1610
1620
  */
1611
- get: {
1621
+ delete: {
1612
1622
  parameters: {
1613
1623
  query?: never;
1614
1624
  header?: never;
@@ -1619,14 +1629,12 @@ export interface paths {
1619
1629
  };
1620
1630
  requestBody?: never;
1621
1631
  responses: {
1622
- /** @description 5-bucket quota snapshot */
1623
- 200: {
1632
+ /** @description Scheduled variant change cancelled */
1633
+ 204: {
1624
1634
  headers: {
1625
1635
  [name: string]: unknown;
1626
1636
  };
1627
- content: {
1628
- "application/json": components["schemas"]["QuotaEnvelope"];
1629
- };
1637
+ content?: never;
1630
1638
  };
1631
1639
  /** @description Bad request — validation failed */
1632
1640
  400: {
@@ -1664,6 +1672,15 @@ export interface paths {
1664
1672
  "application/json": components["schemas"]["ErrorEnvelope"];
1665
1673
  };
1666
1674
  };
1675
+ /** @description Conflict — concurrent or terminal state */
1676
+ 409: {
1677
+ headers: {
1678
+ [name: string]: unknown;
1679
+ };
1680
+ content: {
1681
+ "application/json": components["schemas"]["ErrorEnvelope"];
1682
+ };
1683
+ };
1667
1684
  /** @description Too many requests — rate limit exceeded */
1668
1685
  429: {
1669
1686
  headers: {
@@ -1684,47 +1701,52 @@ export interface paths {
1684
1701
  };
1685
1702
  };
1686
1703
  };
1687
- put?: never;
1688
- post?: never;
1689
- delete?: never;
1690
1704
  options?: never;
1691
1705
  head?: never;
1692
1706
  patch?: never;
1693
1707
  trace?: never;
1694
1708
  };
1695
- "/v1/lines/{id}/queue": {
1709
+ "/v1/lines/{id}/terminate": {
1696
1710
  parameters: {
1697
1711
  query?: never;
1698
1712
  header?: never;
1699
1713
  path?: never;
1700
1714
  cookie?: never;
1701
1715
  };
1716
+ get?: never;
1717
+ put?: never;
1702
1718
  /**
1703
- * Inspect a line's queued and scheduled messages
1704
- * @description Cursor-paginated queue snapshot: per-row `to` / `body` / `status` (`queued` / `scheduled` / `delivered_to_device`) + the schedule reason and the activity-curve hour. Companion to `GET /v1/lines/{id}/quota` for partners debugging delivery delays.
1719
+ * Terminate a line you own
1720
+ * @description Permanently END an active line the caller owns — the self-serve counterpart to the admin terminate. Use this when your own customer cancels: `/release` only returns a *reserved* marketplace line to the shelf, while this ends a live `rental` line or a virtual `shared` line for good. The line flips to the terminal `cancelled` status and keeps its owner (who held it when it died is history, not a shelf return); queued messages fail with `carrier_terminated`, shared-fleet bindings are released (`binding.released`), device sessions are revoked, and `line.disconnected` fires with `reason: customer_initiated`. Billing stops in both shapes: a per-line Stripe subscription is cancelled immediately (not at period end), and a wholesale partner's end-of-month active-line count drops the line in the same breath. Dedicated hardware goes back to iSnap stock — the eSIM and Apple ID are recycled, never scrapped, because a customer ending a rental says nothing about whether the carrier plan is still good. **Idempotent**: terminating an already-terminated line returns 200 with the line unchanged and fires nothing a second time. Only an `active` or `suspended` line — the ones you are actually being billed for — is terminable: a merely `reserved` line is a 15-minute hold you give back with `/release`, and a `provisioning` pre-order line is cancelled through `/v1/pre-orders/{id}/cancel`. A BYOD line is terminable like any other: its device session is revoked, so the bridge or phone stops serving it, and it leaves the billable set; a direct customer's slot is freed for a new pairing. Owned test lines are refused (403) until converted back with `/convert-from-test`.
1705
1721
  */
1706
- get: {
1722
+ post: {
1707
1723
  parameters: {
1708
- query?: {
1709
- limit?: number;
1710
- cursor?: string;
1711
- status?: "queued" | "scheduled" | "delivered_to_device";
1712
- };
1724
+ query?: never;
1713
1725
  header?: never;
1714
1726
  path: {
1715
1727
  id: string;
1716
1728
  };
1717
1729
  cookie?: never;
1718
1730
  };
1719
- requestBody?: never;
1731
+ requestBody?: {
1732
+ content: {
1733
+ "application/json": components["schemas"]["TerminateLine"];
1734
+ };
1735
+ };
1720
1736
  responses: {
1721
- /** @description Queue inspection — summary + paginated items */
1737
+ /** @description Line terminated (or already terminated — the call is idempotent). Returns the line at its terminal `cancelled` status. */
1722
1738
  200: {
1723
1739
  headers: {
1724
1740
  [name: string]: unknown;
1725
1741
  };
1726
1742
  content: {
1727
- "application/json": components["schemas"]["QueueEnvelope"];
1743
+ "application/json": {
1744
+ /** @enum {boolean} */
1745
+ success: true;
1746
+ data: components["schemas"]["Line"];
1747
+ trace_id: string;
1748
+ request_id: string;
1749
+ };
1728
1750
  };
1729
1751
  };
1730
1752
  /** @description Bad request — validation failed */
@@ -1763,6 +1785,15 @@ export interface paths {
1763
1785
  "application/json": components["schemas"]["ErrorEnvelope"];
1764
1786
  };
1765
1787
  };
1788
+ /** @description Conflict — concurrent or terminal state */
1789
+ 409: {
1790
+ headers: {
1791
+ [name: string]: unknown;
1792
+ };
1793
+ content: {
1794
+ "application/json": components["schemas"]["ErrorEnvelope"];
1795
+ };
1796
+ };
1766
1797
  /** @description Too many requests — rate limit exceeded */
1767
1798
  429: {
1768
1799
  headers: {
@@ -1783,26 +1814,26 @@ export interface paths {
1783
1814
  };
1784
1815
  };
1785
1816
  };
1786
- put?: never;
1787
- post?: never;
1788
1817
  delete?: never;
1789
1818
  options?: never;
1790
1819
  head?: never;
1791
1820
  patch?: never;
1792
1821
  trace?: never;
1793
1822
  };
1794
- "/v1/lines/{id}/health": {
1823
+ "/v1/lines/{id}/cancel": {
1795
1824
  parameters: {
1796
1825
  query?: never;
1797
1826
  header?: never;
1798
1827
  path?: never;
1799
1828
  cookie?: never;
1800
1829
  };
1830
+ get?: never;
1831
+ put?: never;
1801
1832
  /**
1802
- * Get a BYOD line's health snapshot
1803
- * @description BYOD-only health rollup. `state` (`active` / `degraded` / `offline`) is derived from the device's fundamental capabilities — min(send_text, imessage) — with offline (no heartbeat) taking precedence. Also returns `runtime_capabilities` (the per-key tri-valued descriptor the device last reported), last heartbeat timestamp, daily quota headroom, Apple-ID-flagged flag, last-error string, bridge version. Rental tiers receive 403 `rental_health_not_exposed` — they don't own the hardware and surfacing transient state creates support-ticket noise.
1833
+ * Cancel a line at its next billing anniversary
1834
+ * @description Schedule the END of a line you own for its next billing anniversary, and keep using it until then. This is the verb for "my own customer cancelled": the month is already paid for and iSnap never prorates, so terminating on the spot would destroy days you have bought. The line stays fully `active` — it sends, receives and bills exactly as before — and iSnap terminates it itself when `cancel_at` arrives, firing `line.terminated` with `origin: scheduled_cancellation`. The date is the next occurrence of `anniversary_at` strictly after now, clamped to the last day of a shorter month (a line taken on the 31st renews on the 28th in February and on the 31st again in March — the clamp never sticks). **Idempotent**: cancelling an already-cancelled line returns the SAME `cancel_at`, never a recomputed one, so a retry that happens to cross the anniversary cannot push your own cancellation a month into the future. Undo it with `/uncancel` any time before the date. Refused with 409 `line_has_no_anniversary` on a line that has none — state it with `/anniversary`, or end the line now with `/terminate`. Rental, shared and BYOD lines are all cancellable; a BYOD line is terminated at its date like the others.
1804
1835
  */
1805
- get: {
1836
+ post: {
1806
1837
  parameters: {
1807
1838
  query?: never;
1808
1839
  header?: never;
@@ -1813,13 +1844,19 @@ export interface paths {
1813
1844
  };
1814
1845
  requestBody?: never;
1815
1846
  responses: {
1816
- /** @description BYOD line health snapshot */
1847
+ /** @description Cancellation scheduled (or already scheduled — the call is idempotent). Returns the line, still `active`, with `cancel_at` set. */
1817
1848
  200: {
1818
1849
  headers: {
1819
1850
  [name: string]: unknown;
1820
1851
  };
1821
1852
  content: {
1822
- "application/json": components["schemas"]["LineHealthEnvelope"];
1853
+ "application/json": {
1854
+ /** @enum {boolean} */
1855
+ success: true;
1856
+ data: components["schemas"]["Line"];
1857
+ trace_id: string;
1858
+ request_id: string;
1859
+ };
1823
1860
  };
1824
1861
  };
1825
1862
  /** @description Bad request — validation failed */
@@ -1858,6 +1895,15 @@ export interface paths {
1858
1895
  "application/json": components["schemas"]["ErrorEnvelope"];
1859
1896
  };
1860
1897
  };
1898
+ /** @description Conflict — concurrent or terminal state */
1899
+ 409: {
1900
+ headers: {
1901
+ [name: string]: unknown;
1902
+ };
1903
+ content: {
1904
+ "application/json": components["schemas"]["ErrorEnvelope"];
1905
+ };
1906
+ };
1861
1907
  /** @description Too many requests — rate limit exceeded */
1862
1908
  429: {
1863
1909
  headers: {
@@ -1878,26 +1924,26 @@ export interface paths {
1878
1924
  };
1879
1925
  };
1880
1926
  };
1881
- put?: never;
1882
- post?: never;
1883
1927
  delete?: never;
1884
1928
  options?: never;
1885
1929
  head?: never;
1886
1930
  patch?: never;
1887
1931
  trace?: never;
1888
1932
  };
1889
- "/v1/lines/{id}/config": {
1933
+ "/v1/lines/{id}/uncancel": {
1890
1934
  parameters: {
1891
1935
  query?: never;
1892
1936
  header?: never;
1893
1937
  path?: never;
1894
1938
  cookie?: never;
1895
1939
  };
1940
+ get?: never;
1941
+ put?: never;
1896
1942
  /**
1897
- * Get a line's effective send configuration
1898
- * @description Return the resolved send configuration for this line — tier defaults merged with per-line overrides. Surfaces effective quota caps, timezone, activity-curve target hour, and the `overridden_keys` list so partners can see which fields they've customised.
1943
+ * Undo a scheduled cancellation
1944
+ * @description Clear a pending cancellation: `cancel_at` goes back to `null` and the line resumes renewing at its anniversary like any other. Nothing about the line changed while it was cancelled — it never stopped working — so there is nothing to restore. **A no-op on a line that is not cancelled**: 200 with the line and `cancel_at: null`, which is the honest answer to "make sure this line is not cancelled" and lets a reconciliation loop run without treating its own success as an error. Refused with 409 `line_already_terminated` once the anniversary has passed and iSnap has ended the line — that cannot be undone; order a new line instead.
1899
1945
  */
1900
- get: {
1946
+ post: {
1901
1947
  parameters: {
1902
1948
  query?: never;
1903
1949
  header?: never;
@@ -1908,13 +1954,19 @@ export interface paths {
1908
1954
  };
1909
1955
  requestBody?: never;
1910
1956
  responses: {
1911
- /** @description Effective config (tier defaults + per-line overrides) */
1957
+ /** @description Cancellation cleared, or the line was not cancelled. Returns the line with `cancel_at: null`. */
1912
1958
  200: {
1913
1959
  headers: {
1914
1960
  [name: string]: unknown;
1915
1961
  };
1916
1962
  content: {
1917
- "application/json": components["schemas"]["LineConfigEnvelope"];
1963
+ "application/json": {
1964
+ /** @enum {boolean} */
1965
+ success: true;
1966
+ data: components["schemas"]["Line"];
1967
+ trace_id: string;
1968
+ request_id: string;
1969
+ };
1918
1970
  };
1919
1971
  };
1920
1972
  /** @description Bad request — validation failed */
@@ -1953,6 +2005,15 @@ export interface paths {
1953
2005
  "application/json": components["schemas"]["ErrorEnvelope"];
1954
2006
  };
1955
2007
  };
2008
+ /** @description Conflict — concurrent or terminal state */
2009
+ 409: {
2010
+ headers: {
2011
+ [name: string]: unknown;
2012
+ };
2013
+ content: {
2014
+ "application/json": components["schemas"]["ErrorEnvelope"];
2015
+ };
2016
+ };
1956
2017
  /** @description Too many requests — rate limit exceeded */
1957
2018
  429: {
1958
2019
  headers: {
@@ -1973,16 +2034,26 @@ export interface paths {
1973
2034
  };
1974
2035
  };
1975
2036
  };
1976
- put?: never;
1977
- post?: never;
1978
2037
  delete?: never;
1979
2038
  options?: never;
1980
2039
  head?: never;
2040
+ patch?: never;
2041
+ trace?: never;
2042
+ };
2043
+ "/v1/lines/{id}/anniversary": {
2044
+ parameters: {
2045
+ query?: never;
2046
+ header?: never;
2047
+ path?: never;
2048
+ cookie?: never;
2049
+ };
2050
+ get?: never;
2051
+ put?: never;
1981
2052
  /**
1982
- * Update a line's send configuration
1983
- * @description Merge-patch the line's per-line config overrides (quota caps, timezone, activity curve). Pass `null` for a key to revert it to the tier default. Validates the activity curve sums to 1.0 and rejects an unknown timezone with 400.
2053
+ * State the day you took this line
2054
+ * @description Record the instant you took this line, which becomes the day of the month it renews on and the date `/cancel` waits for. **This is for lines migrated from your own system**, whose real start date iSnap cannot know: a line taken through the iSnap API is stamped automatically at activation and needs no call. A BYOD line is accepted here even though `/cancel` refuses it: it is on the same monthly meter and carries an anniversary, iSnap simply has no way to end it for you at that date yet. Overwrites an existing anniversary — a date somebody states beats a date iSnap inferred — but is refused with 409 `line_cancellation_pending` while a cancellation is scheduled, because that `cancel_at` was computed from the anniversary you are replacing and would otherwise end the line on a day nothing explains: uncancel, set the date, cancel again. Audited as `line.anniversary_set` with the previous value.
1984
2055
  */
1985
- patch: {
2056
+ post: {
1986
2057
  parameters: {
1987
2058
  query?: never;
1988
2059
  header?: never;
@@ -1993,17 +2064,23 @@ export interface paths {
1993
2064
  };
1994
2065
  requestBody?: {
1995
2066
  content: {
1996
- "application/json": components["schemas"]["LineConfigPatch"];
2067
+ "application/json": components["schemas"]["SetLineAnniversary"];
1997
2068
  };
1998
2069
  };
1999
2070
  responses: {
2000
- /** @description Updated effective config (merged) + overridden_keys */
2071
+ /** @description Anniversary recorded. Returns the line with the new `anniversary_at`. */
2001
2072
  200: {
2002
2073
  headers: {
2003
2074
  [name: string]: unknown;
2004
2075
  };
2005
2076
  content: {
2006
- "application/json": components["schemas"]["LineConfigEnvelope"];
2077
+ "application/json": {
2078
+ /** @enum {boolean} */
2079
+ success: true;
2080
+ data: components["schemas"]["Line"];
2081
+ trace_id: string;
2082
+ request_id: string;
2083
+ };
2007
2084
  };
2008
2085
  };
2009
2086
  /** @description Bad request — validation failed */
@@ -2042,6 +2119,15 @@ export interface paths {
2042
2119
  "application/json": components["schemas"]["ErrorEnvelope"];
2043
2120
  };
2044
2121
  };
2122
+ /** @description Conflict — concurrent or terminal state */
2123
+ 409: {
2124
+ headers: {
2125
+ [name: string]: unknown;
2126
+ };
2127
+ content: {
2128
+ "application/json": components["schemas"]["ErrorEnvelope"];
2129
+ };
2130
+ };
2045
2131
  /** @description Too many requests — rate limit exceeded */
2046
2132
  429: {
2047
2133
  headers: {
@@ -2062,39 +2148,49 @@ export interface paths {
2062
2148
  };
2063
2149
  };
2064
2150
  };
2151
+ delete?: never;
2152
+ options?: never;
2153
+ head?: never;
2154
+ patch?: never;
2065
2155
  trace?: never;
2066
2156
  };
2067
- "/v1/pre-orders": {
2157
+ "/v1/lines/{id}/release": {
2068
2158
  parameters: {
2069
2159
  query?: never;
2070
2160
  header?: never;
2071
2161
  path?: never;
2072
2162
  cookie?: never;
2073
2163
  };
2164
+ get?: never;
2165
+ put?: never;
2074
2166
  /**
2075
- * List pre-orders
2076
- * @description Cursor-paginated list of the caller's pre-orders. Filter by status (`pending`, `provisioning`, `ready`, `fulfilled`, `cancelled`, `refunded`) to surface only the ones the dashboard needs.
2167
+ * Release a line back to the marketplace
2168
+ * @description Cancel the line's ownership and return it to `available`. Cancels the underlying Stripe subscription at period end. The caller must own the line. See `/concepts/billing` for the proration rules.
2077
2169
  */
2078
- get: {
2170
+ post: {
2079
2171
  parameters: {
2080
- query?: {
2081
- status?: "draft" | "pending" | "provisioning" | "ready" | "fulfilled" | "cancelled" | "refunded";
2082
- cursor?: string;
2083
- limit?: number;
2084
- };
2172
+ query?: never;
2085
2173
  header?: never;
2086
- path?: never;
2174
+ path: {
2175
+ id: string;
2176
+ };
2087
2177
  cookie?: never;
2088
2178
  };
2089
2179
  requestBody?: never;
2090
2180
  responses: {
2091
- /** @description Pre-order list */
2181
+ /** @description Line released */
2092
2182
  200: {
2093
2183
  headers: {
2094
2184
  [name: string]: unknown;
2095
2185
  };
2096
2186
  content: {
2097
- "application/json": components["schemas"]["PreOrderList"];
2187
+ "application/json": {
2188
+ /** @enum {boolean} */
2189
+ success: true;
2190
+ data: components["schemas"]["Line"];
2191
+ trace_id: string;
2192
+ request_id: string;
2193
+ };
2098
2194
  };
2099
2195
  };
2100
2196
  /** @description Bad request — validation failed */
@@ -2115,6 +2211,24 @@ export interface paths {
2115
2211
  "application/json": components["schemas"]["ErrorEnvelope"];
2116
2212
  };
2117
2213
  };
2214
+ /** @description Forbidden — caller authenticated but not allowed */
2215
+ 403: {
2216
+ headers: {
2217
+ [name: string]: unknown;
2218
+ };
2219
+ content: {
2220
+ "application/json": components["schemas"]["ErrorEnvelope"];
2221
+ };
2222
+ };
2223
+ /** @description Not found */
2224
+ 404: {
2225
+ headers: {
2226
+ [name: string]: unknown;
2227
+ };
2228
+ content: {
2229
+ "application/json": components["schemas"]["ErrorEnvelope"];
2230
+ };
2231
+ };
2118
2232
  /** @description Too many requests — rate limit exceeded */
2119
2233
  429: {
2120
2234
  headers: {
@@ -2135,12 +2249,556 @@ export interface paths {
2135
2249
  };
2136
2250
  };
2137
2251
  };
2138
- put?: never;
2252
+ delete?: never;
2253
+ options?: never;
2254
+ head?: never;
2255
+ patch?: never;
2256
+ trace?: never;
2257
+ };
2258
+ "/v1/lines/{id}/quota": {
2259
+ parameters: {
2260
+ query?: never;
2261
+ header?: never;
2262
+ path?: never;
2263
+ cookie?: never;
2264
+ };
2139
2265
  /**
2140
- * Create a pre-order (draft)
2141
- * @description Place a pre-order as a `draft` (WHA-1155). A draft has NO side effects — no provisioning line, no procurement, no Stripe charge — so the partner can hold intent / bill its own end-user first. Call `POST /v1/pre-orders/{id}/confirm` to engage the month and start procurement. `inbound_only` is remembered for confirm.
2266
+ * Get a line's quota snapshot
2267
+ * @description Five-bucket quota snapshot: new-contacts, known-contacts, total daily messages, hourly, and minute counters with their effective limits plus 80%-warning flags, alongside the reply lane's rolling budget. Each bucket also reports `*_frees_at` — the instant it drops back under its limit, or `null` when it has room now. Drives the dashboard usage panel and partner self-service throttle decisions.
2142
2268
  */
2143
- post: {
2269
+ get: {
2270
+ parameters: {
2271
+ query?: never;
2272
+ header?: never;
2273
+ path: {
2274
+ id: string;
2275
+ };
2276
+ cookie?: never;
2277
+ };
2278
+ requestBody?: never;
2279
+ responses: {
2280
+ /** @description 5-bucket quota snapshot */
2281
+ 200: {
2282
+ headers: {
2283
+ [name: string]: unknown;
2284
+ };
2285
+ content: {
2286
+ "application/json": components["schemas"]["QuotaEnvelope"];
2287
+ };
2288
+ };
2289
+ /** @description Bad request — validation failed */
2290
+ 400: {
2291
+ headers: {
2292
+ [name: string]: unknown;
2293
+ };
2294
+ content: {
2295
+ "application/json": components["schemas"]["ErrorEnvelope"];
2296
+ };
2297
+ };
2298
+ /** @description Unauthenticated — missing or invalid bearer token */
2299
+ 401: {
2300
+ headers: {
2301
+ [name: string]: unknown;
2302
+ };
2303
+ content: {
2304
+ "application/json": components["schemas"]["ErrorEnvelope"];
2305
+ };
2306
+ };
2307
+ /** @description Forbidden — caller authenticated but not allowed */
2308
+ 403: {
2309
+ headers: {
2310
+ [name: string]: unknown;
2311
+ };
2312
+ content: {
2313
+ "application/json": components["schemas"]["ErrorEnvelope"];
2314
+ };
2315
+ };
2316
+ /** @description Not found */
2317
+ 404: {
2318
+ headers: {
2319
+ [name: string]: unknown;
2320
+ };
2321
+ content: {
2322
+ "application/json": components["schemas"]["ErrorEnvelope"];
2323
+ };
2324
+ };
2325
+ /** @description Too many requests — rate limit exceeded */
2326
+ 429: {
2327
+ headers: {
2328
+ [name: string]: unknown;
2329
+ };
2330
+ content: {
2331
+ "application/json": components["schemas"]["ErrorEnvelope"];
2332
+ };
2333
+ };
2334
+ /** @description Internal server error */
2335
+ 500: {
2336
+ headers: {
2337
+ [name: string]: unknown;
2338
+ };
2339
+ content: {
2340
+ "application/json": components["schemas"]["ErrorEnvelope"];
2341
+ };
2342
+ };
2343
+ };
2344
+ };
2345
+ put?: never;
2346
+ post?: never;
2347
+ delete?: never;
2348
+ options?: never;
2349
+ head?: never;
2350
+ patch?: never;
2351
+ trace?: never;
2352
+ };
2353
+ "/v1/lines/{id}/queue": {
2354
+ parameters: {
2355
+ query?: never;
2356
+ header?: never;
2357
+ path?: never;
2358
+ cookie?: never;
2359
+ };
2360
+ /**
2361
+ * Inspect a line's queued and scheduled messages
2362
+ * @description Cursor-paginated queue snapshot: per-row `to` / `body` / `status` (`queued` / `scheduled` / `delivered_to_device`) + the schedule reason and the activity-curve hour. Companion to `GET /v1/lines/{id}/quota` for partners debugging delivery delays.
2363
+ */
2364
+ get: {
2365
+ parameters: {
2366
+ query?: {
2367
+ limit?: number;
2368
+ cursor?: string;
2369
+ status?: "queued" | "scheduled" | "delivered_to_device";
2370
+ };
2371
+ header?: never;
2372
+ path: {
2373
+ id: string;
2374
+ };
2375
+ cookie?: never;
2376
+ };
2377
+ requestBody?: never;
2378
+ responses: {
2379
+ /** @description Queue inspection — summary + paginated items */
2380
+ 200: {
2381
+ headers: {
2382
+ [name: string]: unknown;
2383
+ };
2384
+ content: {
2385
+ "application/json": components["schemas"]["QueueEnvelope"];
2386
+ };
2387
+ };
2388
+ /** @description Bad request — validation failed */
2389
+ 400: {
2390
+ headers: {
2391
+ [name: string]: unknown;
2392
+ };
2393
+ content: {
2394
+ "application/json": components["schemas"]["ErrorEnvelope"];
2395
+ };
2396
+ };
2397
+ /** @description Unauthenticated — missing or invalid bearer token */
2398
+ 401: {
2399
+ headers: {
2400
+ [name: string]: unknown;
2401
+ };
2402
+ content: {
2403
+ "application/json": components["schemas"]["ErrorEnvelope"];
2404
+ };
2405
+ };
2406
+ /** @description Forbidden — caller authenticated but not allowed */
2407
+ 403: {
2408
+ headers: {
2409
+ [name: string]: unknown;
2410
+ };
2411
+ content: {
2412
+ "application/json": components["schemas"]["ErrorEnvelope"];
2413
+ };
2414
+ };
2415
+ /** @description Not found */
2416
+ 404: {
2417
+ headers: {
2418
+ [name: string]: unknown;
2419
+ };
2420
+ content: {
2421
+ "application/json": components["schemas"]["ErrorEnvelope"];
2422
+ };
2423
+ };
2424
+ /** @description Too many requests — rate limit exceeded */
2425
+ 429: {
2426
+ headers: {
2427
+ [name: string]: unknown;
2428
+ };
2429
+ content: {
2430
+ "application/json": components["schemas"]["ErrorEnvelope"];
2431
+ };
2432
+ };
2433
+ /** @description Internal server error */
2434
+ 500: {
2435
+ headers: {
2436
+ [name: string]: unknown;
2437
+ };
2438
+ content: {
2439
+ "application/json": components["schemas"]["ErrorEnvelope"];
2440
+ };
2441
+ };
2442
+ };
2443
+ };
2444
+ put?: never;
2445
+ post?: never;
2446
+ delete?: never;
2447
+ options?: never;
2448
+ head?: never;
2449
+ patch?: never;
2450
+ trace?: never;
2451
+ };
2452
+ "/v1/lines/{id}/health": {
2453
+ parameters: {
2454
+ query?: never;
2455
+ header?: never;
2456
+ path?: never;
2457
+ cookie?: never;
2458
+ };
2459
+ /**
2460
+ * Get a BYOD line's health snapshot
2461
+ * @description BYOD-only health rollup. `state` (`active` / `degraded` / `offline`) is derived from the device's fundamental capabilities — min(send_text, imessage) — with offline (no heartbeat) taking precedence. Also returns `runtime_capabilities` (the per-key tri-valued descriptor the device last reported), last heartbeat timestamp, daily quota headroom, Apple-ID-flagged flag, last-error string, bridge version. Rental tiers receive 403 `rental_health_not_exposed` — they don't own the hardware and surfacing transient state creates support-ticket noise.
2462
+ */
2463
+ get: {
2464
+ parameters: {
2465
+ query?: never;
2466
+ header?: never;
2467
+ path: {
2468
+ id: string;
2469
+ };
2470
+ cookie?: never;
2471
+ };
2472
+ requestBody?: never;
2473
+ responses: {
2474
+ /** @description BYOD line health snapshot */
2475
+ 200: {
2476
+ headers: {
2477
+ [name: string]: unknown;
2478
+ };
2479
+ content: {
2480
+ "application/json": components["schemas"]["LineHealthEnvelope"];
2481
+ };
2482
+ };
2483
+ /** @description Bad request — validation failed */
2484
+ 400: {
2485
+ headers: {
2486
+ [name: string]: unknown;
2487
+ };
2488
+ content: {
2489
+ "application/json": components["schemas"]["ErrorEnvelope"];
2490
+ };
2491
+ };
2492
+ /** @description Unauthenticated — missing or invalid bearer token */
2493
+ 401: {
2494
+ headers: {
2495
+ [name: string]: unknown;
2496
+ };
2497
+ content: {
2498
+ "application/json": components["schemas"]["ErrorEnvelope"];
2499
+ };
2500
+ };
2501
+ /** @description Forbidden — caller authenticated but not allowed */
2502
+ 403: {
2503
+ headers: {
2504
+ [name: string]: unknown;
2505
+ };
2506
+ content: {
2507
+ "application/json": components["schemas"]["ErrorEnvelope"];
2508
+ };
2509
+ };
2510
+ /** @description Not found */
2511
+ 404: {
2512
+ headers: {
2513
+ [name: string]: unknown;
2514
+ };
2515
+ content: {
2516
+ "application/json": components["schemas"]["ErrorEnvelope"];
2517
+ };
2518
+ };
2519
+ /** @description Too many requests — rate limit exceeded */
2520
+ 429: {
2521
+ headers: {
2522
+ [name: string]: unknown;
2523
+ };
2524
+ content: {
2525
+ "application/json": components["schemas"]["ErrorEnvelope"];
2526
+ };
2527
+ };
2528
+ /** @description Internal server error */
2529
+ 500: {
2530
+ headers: {
2531
+ [name: string]: unknown;
2532
+ };
2533
+ content: {
2534
+ "application/json": components["schemas"]["ErrorEnvelope"];
2535
+ };
2536
+ };
2537
+ };
2538
+ };
2539
+ put?: never;
2540
+ post?: never;
2541
+ delete?: never;
2542
+ options?: never;
2543
+ head?: never;
2544
+ patch?: never;
2545
+ trace?: never;
2546
+ };
2547
+ "/v1/lines/{id}/config": {
2548
+ parameters: {
2549
+ query?: never;
2550
+ header?: never;
2551
+ path?: never;
2552
+ cookie?: never;
2553
+ };
2554
+ /**
2555
+ * Get a line's effective send configuration
2556
+ * @description Return the resolved send configuration for this line — tier defaults merged with per-line overrides. Surfaces effective quota caps, timezone, activity-curve target hour, and the `overridden_keys` list so partners can see which fields they've customised.
2557
+ */
2558
+ get: {
2559
+ parameters: {
2560
+ query?: never;
2561
+ header?: never;
2562
+ path: {
2563
+ id: string;
2564
+ };
2565
+ cookie?: never;
2566
+ };
2567
+ requestBody?: never;
2568
+ responses: {
2569
+ /** @description Effective config (tier defaults + per-line overrides) */
2570
+ 200: {
2571
+ headers: {
2572
+ [name: string]: unknown;
2573
+ };
2574
+ content: {
2575
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2576
+ };
2577
+ };
2578
+ /** @description Bad request — validation failed */
2579
+ 400: {
2580
+ headers: {
2581
+ [name: string]: unknown;
2582
+ };
2583
+ content: {
2584
+ "application/json": components["schemas"]["ErrorEnvelope"];
2585
+ };
2586
+ };
2587
+ /** @description Unauthenticated — missing or invalid bearer token */
2588
+ 401: {
2589
+ headers: {
2590
+ [name: string]: unknown;
2591
+ };
2592
+ content: {
2593
+ "application/json": components["schemas"]["ErrorEnvelope"];
2594
+ };
2595
+ };
2596
+ /** @description Forbidden — caller authenticated but not allowed */
2597
+ 403: {
2598
+ headers: {
2599
+ [name: string]: unknown;
2600
+ };
2601
+ content: {
2602
+ "application/json": components["schemas"]["ErrorEnvelope"];
2603
+ };
2604
+ };
2605
+ /** @description Not found */
2606
+ 404: {
2607
+ headers: {
2608
+ [name: string]: unknown;
2609
+ };
2610
+ content: {
2611
+ "application/json": components["schemas"]["ErrorEnvelope"];
2612
+ };
2613
+ };
2614
+ /** @description Too many requests — rate limit exceeded */
2615
+ 429: {
2616
+ headers: {
2617
+ [name: string]: unknown;
2618
+ };
2619
+ content: {
2620
+ "application/json": components["schemas"]["ErrorEnvelope"];
2621
+ };
2622
+ };
2623
+ /** @description Internal server error */
2624
+ 500: {
2625
+ headers: {
2626
+ [name: string]: unknown;
2627
+ };
2628
+ content: {
2629
+ "application/json": components["schemas"]["ErrorEnvelope"];
2630
+ };
2631
+ };
2632
+ };
2633
+ };
2634
+ put?: never;
2635
+ post?: never;
2636
+ delete?: never;
2637
+ options?: never;
2638
+ head?: never;
2639
+ /**
2640
+ * Update a line's send configuration
2641
+ * @description Merge-patch the line's per-line config overrides (quota caps, timezone, activity curve). Pass `null` for a key to revert it to the tier default. Validates the activity curve sums to 1.0 and rejects an unknown timezone with 400.
2642
+ */
2643
+ patch: {
2644
+ parameters: {
2645
+ query?: never;
2646
+ header?: never;
2647
+ path: {
2648
+ id: string;
2649
+ };
2650
+ cookie?: never;
2651
+ };
2652
+ requestBody?: {
2653
+ content: {
2654
+ "application/json": components["schemas"]["LineConfigPatch"];
2655
+ };
2656
+ };
2657
+ responses: {
2658
+ /** @description Updated effective config (merged) + overridden_keys */
2659
+ 200: {
2660
+ headers: {
2661
+ [name: string]: unknown;
2662
+ };
2663
+ content: {
2664
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2665
+ };
2666
+ };
2667
+ /** @description Bad request — validation failed */
2668
+ 400: {
2669
+ headers: {
2670
+ [name: string]: unknown;
2671
+ };
2672
+ content: {
2673
+ "application/json": components["schemas"]["ErrorEnvelope"];
2674
+ };
2675
+ };
2676
+ /** @description Unauthenticated — missing or invalid bearer token */
2677
+ 401: {
2678
+ headers: {
2679
+ [name: string]: unknown;
2680
+ };
2681
+ content: {
2682
+ "application/json": components["schemas"]["ErrorEnvelope"];
2683
+ };
2684
+ };
2685
+ /** @description Forbidden — caller authenticated but not allowed */
2686
+ 403: {
2687
+ headers: {
2688
+ [name: string]: unknown;
2689
+ };
2690
+ content: {
2691
+ "application/json": components["schemas"]["ErrorEnvelope"];
2692
+ };
2693
+ };
2694
+ /** @description Not found */
2695
+ 404: {
2696
+ headers: {
2697
+ [name: string]: unknown;
2698
+ };
2699
+ content: {
2700
+ "application/json": components["schemas"]["ErrorEnvelope"];
2701
+ };
2702
+ };
2703
+ /** @description Too many requests — rate limit exceeded */
2704
+ 429: {
2705
+ headers: {
2706
+ [name: string]: unknown;
2707
+ };
2708
+ content: {
2709
+ "application/json": components["schemas"]["ErrorEnvelope"];
2710
+ };
2711
+ };
2712
+ /** @description Internal server error */
2713
+ 500: {
2714
+ headers: {
2715
+ [name: string]: unknown;
2716
+ };
2717
+ content: {
2718
+ "application/json": components["schemas"]["ErrorEnvelope"];
2719
+ };
2720
+ };
2721
+ };
2722
+ };
2723
+ trace?: never;
2724
+ };
2725
+ "/v1/pre-orders": {
2726
+ parameters: {
2727
+ query?: never;
2728
+ header?: never;
2729
+ path?: never;
2730
+ cookie?: never;
2731
+ };
2732
+ /**
2733
+ * List pre-orders
2734
+ * @description Cursor-paginated list of the caller's pre-orders. Filter by status (`draft`, `provisioning`, `fulfilled`, `cancelled`) to surface only the ones the dashboard needs.
2735
+ */
2736
+ get: {
2737
+ parameters: {
2738
+ query?: {
2739
+ status?: "draft" | "provisioning" | "fulfilled" | "cancelled";
2740
+ cursor?: string;
2741
+ limit?: number;
2742
+ };
2743
+ header?: never;
2744
+ path?: never;
2745
+ cookie?: never;
2746
+ };
2747
+ requestBody?: never;
2748
+ responses: {
2749
+ /** @description Pre-order list */
2750
+ 200: {
2751
+ headers: {
2752
+ [name: string]: unknown;
2753
+ };
2754
+ content: {
2755
+ "application/json": components["schemas"]["PreOrderList"];
2756
+ };
2757
+ };
2758
+ /** @description Bad request — validation failed */
2759
+ 400: {
2760
+ headers: {
2761
+ [name: string]: unknown;
2762
+ };
2763
+ content: {
2764
+ "application/json": components["schemas"]["ErrorEnvelope"];
2765
+ };
2766
+ };
2767
+ /** @description Unauthenticated — missing or invalid bearer token */
2768
+ 401: {
2769
+ headers: {
2770
+ [name: string]: unknown;
2771
+ };
2772
+ content: {
2773
+ "application/json": components["schemas"]["ErrorEnvelope"];
2774
+ };
2775
+ };
2776
+ /** @description Too many requests — rate limit exceeded */
2777
+ 429: {
2778
+ headers: {
2779
+ [name: string]: unknown;
2780
+ };
2781
+ content: {
2782
+ "application/json": components["schemas"]["ErrorEnvelope"];
2783
+ };
2784
+ };
2785
+ /** @description Internal server error */
2786
+ 500: {
2787
+ headers: {
2788
+ [name: string]: unknown;
2789
+ };
2790
+ content: {
2791
+ "application/json": components["schemas"]["ErrorEnvelope"];
2792
+ };
2793
+ };
2794
+ };
2795
+ };
2796
+ put?: never;
2797
+ /**
2798
+ * Create a pre-order (draft)
2799
+ * @description Place a pre-order as a `draft` (WHA-1155). A draft has NO side effects — no provisioning line, no procurement, no Stripe charge — so the partner can hold intent / bill its own end-user first. Call `POST /v1/pre-orders/{id}/confirm` to engage the month and start procurement. A pre-order carries the area codes you would accept, in order of preference, the plan (`inbound_only`), which is remembered for confirm and read back on every response, and optionally the SKU (`billing_tier`). The tier and the plan are ORTHOGONAL — `rental_iphone` covers both the outbound and the inbound product, which is what `inbound_only` selects — so naming both is never a contradiction. `billing_tier` is optional because a pre-order that omits it is still `rental_iphone`, the only tier this flow mints; when named it is ANSWERED, never discarded: `rental_android` is `400 android_rental_not_purchasable` (no Android product exists in the Stripe catalogue, WHA-2474) and a tier no pre-order can procure — BYOD, virtual shared, pool — is `400 tier_not_pre_orderable`, naming the endpoint to call instead (WHA-2949). The body is otherwise STRICT (WHA-2644) — any other property is a `400`, including `quantity` and `metadata`, which earlier revisions of this contract documented and the server silently discarded.
2800
+ */
2801
+ post: {
2144
2802
  parameters: {
2145
2803
  query?: never;
2146
2804
  header?: never;
@@ -2150,18 +2808,107 @@ export interface paths {
2150
2808
  requestBody?: {
2151
2809
  content: {
2152
2810
  "application/json": {
2153
- /** @enum {string} */
2154
- billing_tier: "rental_iphone" | "rental_android";
2155
2811
  /** @default [] */
2156
2812
  preferred_area_codes?: string[];
2157
2813
  /** @default false */
2158
2814
  inbound_only?: boolean;
2815
+ /** @enum {string} */
2816
+ billing_tier?: "rental_iphone" | "rental_android" | "byod_imessage" | "byod_android" | "shared_pool" | "shared_imessage";
2817
+ /**
2818
+ * Format: date-time
2819
+ * @description The billing anchor of the customer who pays for this line (ISO-8601, UTC) — the ONE date it is billed on. Its day of the month becomes the line’s billing day, clamped to the last day of a shorter month; the time of day is kept, because the anchor is an instant and not a calendar day. State the date your own customer is billed on and every line they hold moves together: at each occurrence the line is charged one full month, and at the FIRST occurrence after activation it is credited for the days the activation month had already paid for past it, at the activation rate. Omit it and the line is billed on its activation instant, which is the behaviour of every line provisioned before anchors existed. Refused (400 `billing_anchor_too_far`) more than 31 days after the activation — that would make the first period longer than a month.
2820
+ * @example 2026-09-11T09:15:00Z
2821
+ */
2822
+ billing_anchor_at?: string;
2823
+ };
2824
+ };
2825
+ };
2826
+ responses: {
2827
+ /** @description Pre-order draft created */
2828
+ 201: {
2829
+ headers: {
2830
+ [name: string]: unknown;
2831
+ };
2832
+ content: {
2833
+ "application/json": {
2834
+ /** @enum {boolean} */
2835
+ success: true;
2836
+ data: components["schemas"]["PreOrder"];
2837
+ trace_id: string;
2838
+ request_id: string;
2839
+ };
2840
+ };
2841
+ };
2842
+ /** @description Bad request — validation failed */
2843
+ 400: {
2844
+ headers: {
2845
+ [name: string]: unknown;
2846
+ };
2847
+ content: {
2848
+ "application/json": components["schemas"]["ErrorEnvelope"];
2849
+ };
2850
+ };
2851
+ /** @description Unauthenticated — missing or invalid bearer token */
2852
+ 401: {
2853
+ headers: {
2854
+ [name: string]: unknown;
2855
+ };
2856
+ content: {
2857
+ "application/json": components["schemas"]["ErrorEnvelope"];
2858
+ };
2859
+ };
2860
+ /** @description Too many requests — rate limit exceeded */
2861
+ 429: {
2862
+ headers: {
2863
+ [name: string]: unknown;
2864
+ };
2865
+ content: {
2866
+ "application/json": components["schemas"]["ErrorEnvelope"];
2867
+ };
2868
+ };
2869
+ /** @description Internal server error */
2870
+ 500: {
2871
+ headers: {
2872
+ [name: string]: unknown;
2159
2873
  };
2874
+ content: {
2875
+ "application/json": components["schemas"]["ErrorEnvelope"];
2876
+ };
2877
+ };
2878
+ };
2879
+ };
2880
+ delete?: never;
2881
+ options?: never;
2882
+ head?: never;
2883
+ patch?: never;
2884
+ trace?: never;
2885
+ };
2886
+ "/v1/pre-orders/{id}/confirm": {
2887
+ parameters: {
2888
+ query?: never;
2889
+ header?: never;
2890
+ path?: never;
2891
+ cookie?: never;
2892
+ };
2893
+ get?: never;
2894
+ put?: never;
2895
+ /**
2896
+ * Confirm a pre-order draft
2897
+ * @description Confirm a `draft` pre-order (WHA-1155). The binding step: engages the one-month commitment (full month, no proration) and triggers procurement — mints the `provisioning` line + fulfillment task, notifies ops, and (for direct customers) creates the first-month Stripe payment intent, returned as `stripe_client_secret`. Only a draft can be confirmed.
2898
+ */
2899
+ post: {
2900
+ parameters: {
2901
+ query?: never;
2902
+ header?: never;
2903
+ path: {
2904
+ id: string;
2160
2905
  };
2906
+ cookie?: never;
2161
2907
  };
2908
+ requestBody?: never;
2162
2909
  responses: {
2163
- /** @description Pre-order draft created */
2164
- 201: {
2910
+ /** @description Pre-order confirmed (now provisioning) */
2911
+ 200: {
2165
2912
  headers: {
2166
2913
  [name: string]: unknown;
2167
2914
  };
@@ -2193,6 +2940,15 @@ export interface paths {
2193
2940
  "application/json": components["schemas"]["ErrorEnvelope"];
2194
2941
  };
2195
2942
  };
2943
+ /** @description Not found */
2944
+ 404: {
2945
+ headers: {
2946
+ [name: string]: unknown;
2947
+ };
2948
+ content: {
2949
+ "application/json": components["schemas"]["ErrorEnvelope"];
2950
+ };
2951
+ };
2196
2952
  /** @description Too many requests — rate limit exceeded */
2197
2953
  429: {
2198
2954
  headers: {
@@ -2219,20 +2975,18 @@ export interface paths {
2219
2975
  patch?: never;
2220
2976
  trace?: never;
2221
2977
  };
2222
- "/v1/pre-orders/{id}/confirm": {
2978
+ "/v1/pre-orders/{id}": {
2223
2979
  parameters: {
2224
2980
  query?: never;
2225
2981
  header?: never;
2226
2982
  path?: never;
2227
2983
  cookie?: never;
2228
2984
  };
2229
- get?: never;
2230
- put?: never;
2231
2985
  /**
2232
- * Confirm a pre-order draft
2233
- * @description Confirm a `draft` pre-order (WHA-1155). The binding step: engages the one-month commitment (full month, no proration) and triggers procurement — mints the `provisioning` line + fulfillment task, notifies ops, and (for direct customers) creates the first-month Stripe payment intent, returned as `stripe_client_secret`. Only a draft can be confirmed.
2986
+ * Get a pre-order
2987
+ * @description Fetch a single pre-order, including its current status, the linked Stripe payment intent, and the fulfilled-line reference if the procurement has completed.
2234
2988
  */
2235
- post: {
2989
+ get: {
2236
2990
  parameters: {
2237
2991
  query?: never;
2238
2992
  header?: never;
@@ -2243,7 +2997,7 @@ export interface paths {
2243
2997
  };
2244
2998
  requestBody?: never;
2245
2999
  responses: {
2246
- /** @description Pre-order confirmed (now provisioning) */
3000
+ /** @description Pre-order */
2247
3001
  200: {
2248
3002
  headers: {
2249
3003
  [name: string]: unknown;
@@ -2305,24 +3059,28 @@ export interface paths {
2305
3059
  };
2306
3060
  };
2307
3061
  };
3062
+ put?: never;
3063
+ post?: never;
2308
3064
  delete?: never;
2309
3065
  options?: never;
2310
3066
  head?: never;
2311
3067
  patch?: never;
2312
3068
  trace?: never;
2313
3069
  };
2314
- "/v1/pre-orders/{id}": {
3070
+ "/v1/pre-orders/{id}/cancel": {
2315
3071
  parameters: {
2316
3072
  query?: never;
2317
3073
  header?: never;
2318
3074
  path?: never;
2319
3075
  cookie?: never;
2320
3076
  };
3077
+ get?: never;
3078
+ put?: never;
2321
3079
  /**
2322
- * Get a pre-order
2323
- * @description Fetch a single pre-order, including its current status, the linked Stripe payment intent, and the fulfilled-line reference if the procurement has completed.
3080
+ * Cancel a pre-order draft
3081
+ * @description Cancel an unconfirmed `draft` pre-order (WHA-1155) — free, nothing was provisioned or charged. A CONFIRMED pre-order cannot be cancelled here (the month is committed, full month no proration); release its provisioned line through the standard line endpoint `POST /v1/lines/{id}/release` instead.
2324
3082
  */
2325
- get: {
3083
+ post: {
2326
3084
  parameters: {
2327
3085
  query?: never;
2328
3086
  header?: never;
@@ -2333,7 +3091,7 @@ export interface paths {
2333
3091
  };
2334
3092
  requestBody?: never;
2335
3093
  responses: {
2336
- /** @description Pre-order */
3094
+ /** @description Pre-order cancelled */
2337
3095
  200: {
2338
3096
  headers: {
2339
3097
  [name: string]: unknown;
@@ -2375,6 +3133,15 @@ export interface paths {
2375
3133
  "application/json": components["schemas"]["ErrorEnvelope"];
2376
3134
  };
2377
3135
  };
3136
+ /** @description Conflict — concurrent or terminal state */
3137
+ 409: {
3138
+ headers: {
3139
+ [name: string]: unknown;
3140
+ };
3141
+ content: {
3142
+ "application/json": components["schemas"]["ErrorEnvelope"];
3143
+ };
3144
+ };
2378
3145
  /** @description Too many requests — rate limit exceeded */
2379
3146
  429: {
2380
3147
  headers: {
@@ -2395,15 +3162,112 @@ export interface paths {
2395
3162
  };
2396
3163
  };
2397
3164
  };
3165
+ delete?: never;
3166
+ options?: never;
3167
+ head?: never;
3168
+ patch?: never;
3169
+ trace?: never;
3170
+ };
3171
+ "/v1/billing/checkout": {
3172
+ parameters: {
3173
+ query?: never;
3174
+ header?: never;
3175
+ path?: never;
3176
+ cookie?: never;
3177
+ };
3178
+ get?: never;
2398
3179
  put?: never;
2399
- post?: never;
3180
+ /**
3181
+ * Start a Stripe Checkout for a reserved line
3182
+ * @description Create a Stripe Checkout Session for the line previously reserved by `POST /v1/lines/{id}/reserve`. Returns the hosted checkout URL — redirect the customer there. The webhook handler activates the line once the session completes.
3183
+ */
3184
+ post: {
3185
+ parameters: {
3186
+ query?: never;
3187
+ header?: never;
3188
+ path?: never;
3189
+ cookie?: never;
3190
+ };
3191
+ requestBody?: {
3192
+ content: {
3193
+ "application/json": {
3194
+ /**
3195
+ * @description Line to check out. Send the `line_<base62>` public ID returned by `POST /v1/lines/{id}/reserve`; the internal UUID is also accepted for backward compatibility. Must be status `reserved`.
3196
+ * @example line_2k9Qp7RtY4mNb8VcXs3Wd6Lf1
3197
+ */
3198
+ line_id: string;
3199
+ /**
3200
+ * @description Buy the INBOUND rental product instead of the outbound one. An inbound line cannot INITIATE a conversation — the contact must message first, after which the exchange is unrestricted; it is NOT receive-only. Rental tiers only. The chosen Stripe price is the source of truth: `lines.inbound_only` is re-derived from its lookup_key on `customer.subscription.created/updated`.
3201
+ * @default false
3202
+ */
3203
+ inbound_only?: boolean;
3204
+ };
3205
+ };
3206
+ };
3207
+ responses: {
3208
+ /** @description Checkout session */
3209
+ 200: {
3210
+ headers: {
3211
+ [name: string]: unknown;
3212
+ };
3213
+ content: {
3214
+ "application/json": components["schemas"]["Checkout"];
3215
+ };
3216
+ };
3217
+ /** @description Bad request — validation failed */
3218
+ 400: {
3219
+ headers: {
3220
+ [name: string]: unknown;
3221
+ };
3222
+ content: {
3223
+ "application/json": components["schemas"]["ErrorEnvelope"];
3224
+ };
3225
+ };
3226
+ /** @description Unauthenticated — missing or invalid bearer token */
3227
+ 401: {
3228
+ headers: {
3229
+ [name: string]: unknown;
3230
+ };
3231
+ content: {
3232
+ "application/json": components["schemas"]["ErrorEnvelope"];
3233
+ };
3234
+ };
3235
+ /** @description Not found */
3236
+ 404: {
3237
+ headers: {
3238
+ [name: string]: unknown;
3239
+ };
3240
+ content: {
3241
+ "application/json": components["schemas"]["ErrorEnvelope"];
3242
+ };
3243
+ };
3244
+ /** @description Too many requests — rate limit exceeded */
3245
+ 429: {
3246
+ headers: {
3247
+ [name: string]: unknown;
3248
+ };
3249
+ content: {
3250
+ "application/json": components["schemas"]["ErrorEnvelope"];
3251
+ };
3252
+ };
3253
+ /** @description Internal server error */
3254
+ 500: {
3255
+ headers: {
3256
+ [name: string]: unknown;
3257
+ };
3258
+ content: {
3259
+ "application/json": components["schemas"]["ErrorEnvelope"];
3260
+ };
3261
+ };
3262
+ };
3263
+ };
2400
3264
  delete?: never;
2401
3265
  options?: never;
2402
3266
  head?: never;
2403
3267
  patch?: never;
2404
3268
  trace?: never;
2405
3269
  };
2406
- "/v1/pre-orders/{id}/cancel": {
3270
+ "/v1/billing/byod-checkout": {
2407
3271
  parameters: {
2408
3272
  query?: never;
2409
3273
  header?: never;
@@ -2413,33 +3277,108 @@ export interface paths {
2413
3277
  get?: never;
2414
3278
  put?: never;
2415
3279
  /**
2416
- * Cancel a pre-order draft
2417
- * @description Cancel an unconfirmed `draft` pre-order (WHA-1155) — free, nothing was provisioned or charged. A CONFIRMED pre-order cannot be cancelled here (the month is committed, full month no proration); release its provisioned line through the standard line endpoint `POST /v1/lines/{id}/release` instead.
3280
+ * Start a Stripe Checkout for BYOD slots
3281
+ * @description Create a Stripe Checkout Session for the BYOD subscription product (`$15/mo`, `$40.50/quarter` or `$150/yr` per slot, `device_count` slots). All three cadences the BYOP catalogue carries are sellable — `GET /pricing` publishes the amount of each. The completed session writes a `byod_subscriptions` row that `POST /v1/byod/pair` reads to enforce slot capacity.
2418
3282
  */
2419
3283
  post: {
2420
3284
  parameters: {
2421
3285
  query?: never;
2422
3286
  header?: never;
2423
- path: {
2424
- id: string;
3287
+ path?: never;
3288
+ cookie?: never;
3289
+ };
3290
+ requestBody?: {
3291
+ content: {
3292
+ "application/json": components["schemas"]["ByodCheckoutBody"];
3293
+ };
3294
+ };
3295
+ responses: {
3296
+ /** @description BYOD checkout session */
3297
+ 200: {
3298
+ headers: {
3299
+ [name: string]: unknown;
3300
+ };
3301
+ content: {
3302
+ "application/json": components["schemas"]["ByodCheckoutEnvelope"];
3303
+ };
3304
+ };
3305
+ /** @description Bad request — validation failed */
3306
+ 400: {
3307
+ headers: {
3308
+ [name: string]: unknown;
3309
+ };
3310
+ content: {
3311
+ "application/json": components["schemas"]["ErrorEnvelope"];
3312
+ };
3313
+ };
3314
+ /** @description Unauthenticated — missing or invalid bearer token */
3315
+ 401: {
3316
+ headers: {
3317
+ [name: string]: unknown;
3318
+ };
3319
+ content: {
3320
+ "application/json": components["schemas"]["ErrorEnvelope"];
3321
+ };
3322
+ };
3323
+ /** @description Too many requests — rate limit exceeded */
3324
+ 429: {
3325
+ headers: {
3326
+ [name: string]: unknown;
3327
+ };
3328
+ content: {
3329
+ "application/json": components["schemas"]["ErrorEnvelope"];
3330
+ };
3331
+ };
3332
+ /** @description Internal server error */
3333
+ 500: {
3334
+ headers: {
3335
+ [name: string]: unknown;
3336
+ };
3337
+ content: {
3338
+ "application/json": components["schemas"]["ErrorEnvelope"];
3339
+ };
2425
3340
  };
3341
+ };
3342
+ };
3343
+ delete?: never;
3344
+ options?: never;
3345
+ head?: never;
3346
+ patch?: never;
3347
+ trace?: never;
3348
+ };
3349
+ "/v1/billing/mac-checkout": {
3350
+ parameters: {
3351
+ query?: never;
3352
+ header?: never;
3353
+ path?: never;
3354
+ cookie?: never;
3355
+ };
3356
+ get?: never;
3357
+ put?: never;
3358
+ /**
3359
+ * Start a Stripe Checkout for hosted macOS sessions
3360
+ * @description Create a Stripe Checkout Session for `quantity` rented macOS sessions, priced on a graduated ladder ($35/$30/$25/$20 per session across the 1–10 / 11–30 / 31–50 / 51+ bands). Two rules are enforced here and nowhere else: the quantity must be a whole number of packs at or above the one-pack minimum (`422 mac_quantity_not_a_pack`, the divisor echoed as `issues[0].min`), and a rented session hosts the bridge for one BYOP iPhone so sessions may not exceed the account’s BYOP lines (`422 mac_sessions_exceed_byop_lines`). Stripe’s hosted UI cannot change the quantity afterwards — use `PATCH /v1/billing/subscriptions/{id}`, which applies the same two rules.
3361
+ */
3362
+ post: {
3363
+ parameters: {
3364
+ query?: never;
3365
+ header?: never;
3366
+ path?: never;
2426
3367
  cookie?: never;
2427
3368
  };
2428
- requestBody?: never;
3369
+ requestBody?: {
3370
+ content: {
3371
+ "application/json": components["schemas"]["MacCheckoutBody"];
3372
+ };
3373
+ };
2429
3374
  responses: {
2430
- /** @description Pre-order cancelled */
3375
+ /** @description macOS session checkout */
2431
3376
  200: {
2432
3377
  headers: {
2433
3378
  [name: string]: unknown;
2434
3379
  };
2435
3380
  content: {
2436
- "application/json": {
2437
- /** @enum {boolean} */
2438
- success: true;
2439
- data: components["schemas"]["PreOrder"];
2440
- trace_id: string;
2441
- request_id: string;
2442
- };
3381
+ "application/json": components["schemas"]["MacCheckoutEnvelope"];
2443
3382
  };
2444
3383
  };
2445
3384
  /** @description Bad request — validation failed */
@@ -2469,8 +3408,8 @@ export interface paths {
2469
3408
  "application/json": components["schemas"]["ErrorEnvelope"];
2470
3409
  };
2471
3410
  };
2472
- /** @description Conflict — concurrent or terminal state */
2473
- 409: {
3411
+ /** @description Unprocessable — domain rule violation */
3412
+ 422: {
2474
3413
  headers: {
2475
3414
  [name: string]: unknown;
2476
3415
  };
@@ -2504,7 +3443,7 @@ export interface paths {
2504
3443
  patch?: never;
2505
3444
  trace?: never;
2506
3445
  };
2507
- "/v1/billing/checkout": {
3446
+ "/v1/billing/subscriptions/{id}": {
2508
3447
  parameters: {
2509
3448
  query?: never;
2510
3449
  header?: never;
@@ -2513,41 +3452,29 @@ export interface paths {
2513
3452
  };
2514
3453
  get?: never;
2515
3454
  put?: never;
3455
+ post?: never;
2516
3456
  /**
2517
- * Start a Stripe Checkout for a reserved line
2518
- * @description Create a Stripe Checkout Session for the line previously reserved by `POST /v1/lines/{id}/reserve`. Returns the hosted checkout URL — redirect the customer there. The webhook handler activates the line once the session completes.
3457
+ * Cancel a subscription
3458
+ * @description Schedule the subscription to cancel at the end of the current billing period. The line remains active until the period rolls over; the partner sees the `cancel_at_period_end: true` flag in `GET /v1/billing/subscriptions` until then.
2519
3459
  */
2520
- post: {
3460
+ delete: {
2521
3461
  parameters: {
2522
3462
  query?: never;
2523
3463
  header?: never;
2524
- path?: never;
2525
- cookie?: never;
2526
- };
2527
- requestBody?: {
2528
- content: {
2529
- "application/json": {
2530
- /**
2531
- * @description Line to check out. Send the `line_<base62>` public ID returned by `POST /v1/lines/{id}/reserve`; the internal UUID is also accepted for backward compatibility. Must be status `reserved`.
2532
- * @example line_2k9Qp7RtY4mNb8VcXs3Wd6Lf1
2533
- */
2534
- line_id: string;
2535
- /**
2536
- * @description Buy the INBOUND rental product instead of the outbound one. An inbound line cannot INITIATE a conversation — the contact must message first, after which the exchange is unrestricted; it is NOT receive-only. Rental tiers only. The chosen Stripe price is the source of truth: `lines.inbound_only` is re-derived from its lookup_key on `customer.subscription.created/updated`.
2537
- * @default false
2538
- */
2539
- inbound_only?: boolean;
2540
- };
3464
+ path: {
3465
+ id: string;
2541
3466
  };
3467
+ cookie?: never;
2542
3468
  };
3469
+ requestBody?: never;
2543
3470
  responses: {
2544
- /** @description Checkout session */
3471
+ /** @description Subscription cancelled */
2545
3472
  200: {
2546
3473
  headers: {
2547
3474
  [name: string]: unknown;
2548
3475
  };
2549
3476
  content: {
2550
- "application/json": components["schemas"]["Checkout"];
3477
+ "application/json": components["schemas"]["BillingMessage"];
2551
3478
  };
2552
3479
  };
2553
3480
  /** @description Bad request — validation failed */
@@ -2577,6 +3504,15 @@ export interface paths {
2577
3504
  "application/json": components["schemas"]["ErrorEnvelope"];
2578
3505
  };
2579
3506
  };
3507
+ /** @description Conflict — concurrent or terminal state */
3508
+ 409: {
3509
+ headers: {
3510
+ [name: string]: unknown;
3511
+ };
3512
+ content: {
3513
+ "application/json": components["schemas"]["ErrorEnvelope"];
3514
+ };
3515
+ };
2580
3516
  /** @description Too many requests — rate limit exceeded */
2581
3517
  429: {
2582
3518
  headers: {
@@ -2597,45 +3533,34 @@ export interface paths {
2597
3533
  };
2598
3534
  };
2599
3535
  };
2600
- delete?: never;
2601
3536
  options?: never;
2602
3537
  head?: never;
2603
- patch?: never;
2604
- trace?: never;
2605
- };
2606
- "/v1/billing/byod-checkout": {
2607
- parameters: {
2608
- query?: never;
2609
- header?: never;
2610
- path?: never;
2611
- cookie?: never;
2612
- };
2613
- get?: never;
2614
- put?: never;
2615
3538
  /**
2616
- * Start a Stripe Checkout for BYOD slots
2617
- * @description Create a Stripe Checkout Session for the BYOD subscription product (`$15/mo` or `$150/yr` per slot, `device_count` slots). The completed session writes a `byod_subscriptions` row that `POST /v1/byod/pair` reads to enforce slot capacity.
3539
+ * Change a subscription’s quantity
3540
+ * @description Set the quantity on the subscription’s single quantity-adjustable item — hosted macOS sessions, or BYOP slots. Both directions of the 1:1 rule are enforced: raising sessions above the account’s BYOP lines is `422 mac_sessions_exceed_byop_lines`, and lowering BYOP below the macOS sessions already rented is `422 byop_reduction_strands_mac_sessions`. A macOS quantity must additionally be a whole pack (`422 mac_quantity_not_a_pack`). A subscription that does not carry exactly one adjustable item — a per-line rental, or a wholesale master subscription whose quantities are pushed by metering — is refused `409 subscription_not_quantity_adjustable`.
2618
3541
  */
2619
- post: {
3542
+ patch: {
2620
3543
  parameters: {
2621
3544
  query?: never;
2622
3545
  header?: never;
2623
- path?: never;
3546
+ path: {
3547
+ id: string;
3548
+ };
2624
3549
  cookie?: never;
2625
3550
  };
2626
3551
  requestBody?: {
2627
3552
  content: {
2628
- "application/json": components["schemas"]["ByodCheckoutBody"];
3553
+ "application/json": components["schemas"]["SubscriptionQuantityBody"];
2629
3554
  };
2630
3555
  };
2631
3556
  responses: {
2632
- /** @description BYOD checkout session */
3557
+ /** @description Quantity updated */
2633
3558
  200: {
2634
3559
  headers: {
2635
3560
  [name: string]: unknown;
2636
3561
  };
2637
3562
  content: {
2638
- "application/json": components["schemas"]["ByodCheckoutEnvelope"];
3563
+ "application/json": components["schemas"]["SubscriptionQuantityEnvelope"];
2639
3564
  };
2640
3565
  };
2641
3566
  /** @description Bad request — validation failed */
@@ -2656,8 +3581,8 @@ export interface paths {
2656
3581
  "application/json": components["schemas"]["ErrorEnvelope"];
2657
3582
  };
2658
3583
  };
2659
- /** @description Too many requests — rate limit exceeded */
2660
- 429: {
3584
+ /** @description Forbidden — caller authenticated but not allowed */
3585
+ 403: {
2661
3586
  headers: {
2662
3587
  [name: string]: unknown;
2663
3588
  };
@@ -2665,8 +3590,8 @@ export interface paths {
2665
3590
  "application/json": components["schemas"]["ErrorEnvelope"];
2666
3591
  };
2667
3592
  };
2668
- /** @description Internal server error */
2669
- 500: {
3593
+ /** @description Not found */
3594
+ 404: {
2670
3595
  headers: {
2671
3596
  [name: string]: unknown;
2672
3597
  };
@@ -2674,45 +3599,8 @@ export interface paths {
2674
3599
  "application/json": components["schemas"]["ErrorEnvelope"];
2675
3600
  };
2676
3601
  };
2677
- };
2678
- };
2679
- delete?: never;
2680
- options?: never;
2681
- head?: never;
2682
- patch?: never;
2683
- trace?: never;
2684
- };
2685
- "/v1/billing/subscriptions": {
2686
- parameters: {
2687
- query?: never;
2688
- header?: never;
2689
- path?: never;
2690
- cookie?: never;
2691
- };
2692
- /**
2693
- * List Stripe subscriptions
2694
- * @description Return every active Stripe subscription on the authenticated user's customer record — both per-line rental subs and BYOD slot subs. Period boundaries, line link, price ID, and cancellation flag are surfaced verbatim from Stripe.
2695
- */
2696
- get: {
2697
- parameters: {
2698
- query?: never;
2699
- header?: never;
2700
- path?: never;
2701
- cookie?: never;
2702
- };
2703
- requestBody?: never;
2704
- responses: {
2705
- /** @description Subscription list */
2706
- 200: {
2707
- headers: {
2708
- [name: string]: unknown;
2709
- };
2710
- content: {
2711
- "application/json": components["schemas"]["SubscriptionListEnvelope"];
2712
- };
2713
- };
2714
- /** @description Bad request — validation failed */
2715
- 400: {
3602
+ /** @description Conflict — concurrent or terminal state */
3603
+ 409: {
2716
3604
  headers: {
2717
3605
  [name: string]: unknown;
2718
3606
  };
@@ -2720,8 +3608,8 @@ export interface paths {
2720
3608
  "application/json": components["schemas"]["ErrorEnvelope"];
2721
3609
  };
2722
3610
  };
2723
- /** @description Unauthenticated — missing or invalid bearer token */
2724
- 401: {
3611
+ /** @description Unprocessable — domain rule violation */
3612
+ 422: {
2725
3613
  headers: {
2726
3614
  [name: string]: unknown;
2727
3615
  };
@@ -2749,15 +3637,9 @@ export interface paths {
2749
3637
  };
2750
3638
  };
2751
3639
  };
2752
- put?: never;
2753
- post?: never;
2754
- delete?: never;
2755
- options?: never;
2756
- head?: never;
2757
- patch?: never;
2758
3640
  trace?: never;
2759
3641
  };
2760
- "/v1/billing/invoices": {
3642
+ "/v1/billing/subscriptions": {
2761
3643
  parameters: {
2762
3644
  query?: never;
2763
3645
  header?: never;
@@ -2765,8 +3647,8 @@ export interface paths {
2765
3647
  cookie?: never;
2766
3648
  };
2767
3649
  /**
2768
- * List Stripe invoices
2769
- * @description List the authenticated user's Stripe invoices in reverse-chronological order. Each invoice exposes the period boundaries (unix seconds), the integer-cent amounts, the hosted invoice URL, and the PDF link.
3650
+ * List Stripe subscriptions
3651
+ * @description Return every active Stripe subscription on the authenticated user's customer record — both per-line rental subs and BYOD slot subs. Period boundaries, line link, price ID, and cancellation flag are surfaced verbatim from Stripe.
2770
3652
  */
2771
3653
  get: {
2772
3654
  parameters: {
@@ -2777,13 +3659,13 @@ export interface paths {
2777
3659
  };
2778
3660
  requestBody?: never;
2779
3661
  responses: {
2780
- /** @description Invoice list */
3662
+ /** @description Subscription list */
2781
3663
  200: {
2782
3664
  headers: {
2783
3665
  [name: string]: unknown;
2784
3666
  };
2785
3667
  content: {
2786
- "application/json": components["schemas"]["InvoiceListEnvelope"];
3668
+ "application/json": components["schemas"]["SubscriptionListEnvelope"];
2787
3669
  };
2788
3670
  };
2789
3671
  /** @description Bad request — validation failed */
@@ -2832,38 +3714,33 @@ export interface paths {
2832
3714
  patch?: never;
2833
3715
  trace?: never;
2834
3716
  };
2835
- "/v1/billing/subscriptions/{id}": {
3717
+ "/v1/billing/invoices": {
2836
3718
  parameters: {
2837
3719
  query?: never;
2838
3720
  header?: never;
2839
3721
  path?: never;
2840
3722
  cookie?: never;
2841
3723
  };
2842
- get?: never;
2843
- put?: never;
2844
- post?: never;
2845
3724
  /**
2846
- * Cancel a subscription
2847
- * @description Schedule the subscription to cancel at the end of the current billing period. The line remains active until the period rolls over; the partner sees the `cancel_at_period_end: true` flag in `GET /v1/billing/subscriptions` until then.
3725
+ * List Stripe invoices
3726
+ * @description List the authenticated user's Stripe invoices in reverse-chronological order. Each invoice exposes the period boundaries (unix seconds), the integer-cent amounts, the hosted invoice URL, and the PDF link.
2848
3727
  */
2849
- delete: {
3728
+ get: {
2850
3729
  parameters: {
2851
3730
  query?: never;
2852
3731
  header?: never;
2853
- path: {
2854
- id: string;
2855
- };
3732
+ path?: never;
2856
3733
  cookie?: never;
2857
3734
  };
2858
3735
  requestBody?: never;
2859
3736
  responses: {
2860
- /** @description Subscription cancelled */
3737
+ /** @description Invoice list */
2861
3738
  200: {
2862
3739
  headers: {
2863
3740
  [name: string]: unknown;
2864
3741
  };
2865
3742
  content: {
2866
- "application/json": components["schemas"]["BillingMessage"];
3743
+ "application/json": components["schemas"]["InvoiceListEnvelope"];
2867
3744
  };
2868
3745
  };
2869
3746
  /** @description Bad request — validation failed */
@@ -2884,24 +3761,6 @@ export interface paths {
2884
3761
  "application/json": components["schemas"]["ErrorEnvelope"];
2885
3762
  };
2886
3763
  };
2887
- /** @description Not found */
2888
- 404: {
2889
- headers: {
2890
- [name: string]: unknown;
2891
- };
2892
- content: {
2893
- "application/json": components["schemas"]["ErrorEnvelope"];
2894
- };
2895
- };
2896
- /** @description Conflict — concurrent or terminal state */
2897
- 409: {
2898
- headers: {
2899
- [name: string]: unknown;
2900
- };
2901
- content: {
2902
- "application/json": components["schemas"]["ErrorEnvelope"];
2903
- };
2904
- };
2905
3764
  /** @description Too many requests — rate limit exceeded */
2906
3765
  429: {
2907
3766
  headers: {
@@ -2922,6 +3781,9 @@ export interface paths {
2922
3781
  };
2923
3782
  };
2924
3783
  };
3784
+ put?: never;
3785
+ post?: never;
3786
+ delete?: never;
2925
3787
  options?: never;
2926
3788
  head?: never;
2927
3789
  patch?: never;
@@ -3242,15 +4104,21 @@ export interface paths {
3242
4104
  };
3243
4105
  /**
3244
4106
  * List messages
3245
- * @description Cursor-paginated list of messages, newest first. Each item carries the full `MessageV1` shape — partners can write a uniform handler over both `GET /v1/messages` and `GET /v1/messages/{id}`. Filter by `line_id`, `status`, `direction`, or `kind` (`message` drops the separate reaction/voice rows for a clean feed; `reaction`/`voice` fetch only those; omit for all kinds); pass an opaque `cursor` from the previous page's `next_cursor` to advance.
4107
+ * @description Cursor-paginated list of messages, newest first by default. Each item carries the full `MessageV1` shape — partners can write a uniform handler over both `GET /v1/messages` and `GET /v1/messages/{id}`. Filter by `line_id`, `status`, `direction`, or `kind` (`message` drops the separate reaction/voice rows for a clean feed; `reaction`/`voice` fetch only those; omit for all kinds); pass an opaque `cursor` from the previous page's `next_cursor` to advance. `sort=scheduled_for` serves the queue in departure order instead (rows with no `scheduled_for` last in either direction); `order` flips the direction. A cursor belongs to the sort it was minted for — replaying it under a different `sort`/`order` restarts from the first page.
3246
4108
  */
3247
4109
  get: {
3248
4110
  parameters: {
3249
4111
  query?: {
3250
4112
  line_id?: string;
3251
- status?: "queued" | "sent" | "delivered" | "read" | "failed";
4113
+ status?: "queued" | "scheduled" | "sent" | "delivered" | "read" | "failed" | "cancelled";
3252
4114
  direction?: "outbound" | "inbound";
4115
+ since?: string;
4116
+ until?: string;
3253
4117
  kind?: "message" | "voice" | "reaction";
4118
+ /** @description Ordering key. `created_at` (default) is arrival order; `scheduled_for` is departure order — the same instant the `scheduled_for` field reads back, with rows that have none last in both directions. */
4119
+ sort?: "created_at" | "scheduled_for";
4120
+ /** @description Ordering direction. Defaults to `desc` for `sort=created_at` (newest first) and `asc` for `sort=scheduled_for` (soonest first). */
4121
+ order?: "asc" | "desc";
3254
4122
  cursor?: string;
3255
4123
  limit?: number;
3256
4124
  };
@@ -3926,7 +4794,7 @@ export interface paths {
3926
4794
  };
3927
4795
  };
3928
4796
  };
3929
- /** @description Bad request — validation failed */
4797
+ /** @description Bad request. `validation_failed` — the body does not match the schema. `webhook_url_not_allowed` — the URL is refused by the SSRF guard; `error.issues[0].code` names why: `not_https` (only https:// is accepted), `ssrf_blocked` (the host is, or resolves to, a loopback, link-local, private, carrier-grade NAT or otherwise non-public address — every resolved address is checked), `dns_failed` (the host does not resolve) or `invalid_url`. The same guard runs again before every delivery: a subscription whose host later resolves to a non-public address gets no request, and is suspended. */
3930
4798
  400: {
3931
4799
  headers: {
3932
4800
  [name: string]: unknown;
@@ -4156,7 +5024,7 @@ export interface paths {
4156
5024
  "application/json": components["schemas"]["WebhookV1Envelope"];
4157
5025
  };
4158
5026
  };
4159
- /** @description Bad request — validation failed */
5027
+ /** @description Bad request. `validation_failed` — the body does not match the schema. `webhook_url_not_allowed` — the URL is refused by the SSRF guard; `error.issues[0].code` names why: `not_https` (only https:// is accepted), `ssrf_blocked` (the host is, or resolves to, a loopback, link-local, private, carrier-grade NAT or otherwise non-public address — every resolved address is checked), `dns_failed` (the host does not resolve) or `invalid_url`. The same guard runs again before every delivery: a subscription whose host later resolves to a non-public address gets no request, and is suspended. */
4160
5028
  400: {
4161
5029
  headers: {
4162
5030
  [name: string]: unknown;
@@ -4757,22 +5625,101 @@ export interface paths {
4757
5625
  };
4758
5626
  };
4759
5627
  responses: {
4760
- /** @description Lookup result — cache hit, fast probe, or an un-probed miss */
5628
+ /** @description Lookup result — cache hit, fast probe, or an un-probed miss */
5629
+ 200: {
5630
+ headers: {
5631
+ [name: string]: unknown;
5632
+ };
5633
+ content: {
5634
+ "application/json": components["schemas"]["LookupEnvelope"];
5635
+ };
5636
+ };
5637
+ /** @description Lookup pending — a device probe was dispatched but had not answered within the wait budget; re-poll the same request after `retry_after` seconds */
5638
+ 202: {
5639
+ headers: {
5640
+ [name: string]: unknown;
5641
+ };
5642
+ content: {
5643
+ "application/json": components["schemas"]["LookupEnvelope"];
5644
+ };
5645
+ };
5646
+ /** @description Bad request — validation failed */
5647
+ 400: {
5648
+ headers: {
5649
+ [name: string]: unknown;
5650
+ };
5651
+ content: {
5652
+ "application/json": components["schemas"]["ErrorEnvelope"];
5653
+ };
5654
+ };
5655
+ /** @description Unauthenticated — missing or invalid bearer token */
5656
+ 401: {
5657
+ headers: {
5658
+ [name: string]: unknown;
5659
+ };
5660
+ content: {
5661
+ "application/json": components["schemas"]["ErrorEnvelope"];
5662
+ };
5663
+ };
5664
+ /** @description Too many requests — rate limit exceeded */
5665
+ 429: {
5666
+ headers: {
5667
+ [name: string]: unknown;
5668
+ };
5669
+ content: {
5670
+ "application/json": components["schemas"]["ErrorEnvelope"];
5671
+ };
5672
+ };
5673
+ /** @description Internal server error */
5674
+ 500: {
5675
+ headers: {
5676
+ [name: string]: unknown;
5677
+ };
5678
+ content: {
5679
+ "application/json": components["schemas"]["ErrorEnvelope"];
5680
+ };
5681
+ };
5682
+ };
5683
+ };
5684
+ delete?: never;
5685
+ options?: never;
5686
+ head?: never;
5687
+ patch?: never;
5688
+ trace?: never;
5689
+ };
5690
+ "/v1/attention": {
5691
+ parameters: {
5692
+ query?: never;
5693
+ header?: never;
5694
+ path?: never;
5695
+ cookie?: never;
5696
+ };
5697
+ /**
5698
+ * What currently needs attention on the caller's lines
5699
+ * @description Every line of yours with something wrong: an open incident, a degraded health state, or a fleet probe that has flagged its hardware. One entry per LINE — two faults on one number are one entry. **A BYOD line appears immediately** and carries its reasons. **A rental, pool or shared line appears only once the fault has lasted 15 minutes** (`RENTAL_DURABLE_OFFLINE_MINUTES`, the same threshold as the durable `line.offline` webhook) and carries `reasons: []` with `detail_withheld: true` — per §4.4 a customer with no access to the hardware is told the line is unavailable and being resolved, never which check on which machine failed. `since` is when the fault started; it is null when the underlying detector has no anchor for it. A line-scoped API key sees only the lines in its allowlist.
5700
+ */
5701
+ get: {
5702
+ parameters: {
5703
+ query?: never;
5704
+ header?: never;
5705
+ path?: never;
5706
+ cookie?: never;
5707
+ };
5708
+ requestBody?: never;
5709
+ responses: {
5710
+ /** @description Lines needing attention, longest-standing first */
4761
5711
  200: {
4762
5712
  headers: {
4763
5713
  [name: string]: unknown;
4764
5714
  };
4765
5715
  content: {
4766
- "application/json": components["schemas"]["LookupEnvelope"];
4767
- };
4768
- };
4769
- /** @description Lookup pending — a device probe was dispatched but had not answered within the wait budget; re-poll the same request after `retry_after` seconds */
4770
- 202: {
4771
- headers: {
4772
- [name: string]: unknown;
4773
- };
4774
- content: {
4775
- "application/json": components["schemas"]["LookupEnvelope"];
5716
+ "application/json": {
5717
+ /** @enum {boolean} */
5718
+ success: true;
5719
+ data: components["schemas"]["AttentionList"];
5720
+ trace_id: string;
5721
+ request_id: string;
5722
+ };
4776
5723
  };
4777
5724
  };
4778
5725
  /** @description Bad request — validation failed */
@@ -4813,6 +5760,8 @@ export interface paths {
4813
5760
  };
4814
5761
  };
4815
5762
  };
5763
+ put?: never;
5764
+ post?: never;
4816
5765
  delete?: never;
4817
5766
  options?: never;
4818
5767
  head?: never;
@@ -5407,8 +6356,246 @@ export interface paths {
5407
6356
  "application/json": components["schemas"]["ErrorEnvelope"];
5408
6357
  };
5409
6358
  };
5410
- /** @description Not found */
5411
- 404: {
6359
+ /** @description Not found */
6360
+ 404: {
6361
+ headers: {
6362
+ [name: string]: unknown;
6363
+ };
6364
+ content: {
6365
+ "application/json": components["schemas"]["ErrorEnvelope"];
6366
+ };
6367
+ };
6368
+ /** @description Too many requests — rate limit exceeded */
6369
+ 429: {
6370
+ headers: {
6371
+ [name: string]: unknown;
6372
+ };
6373
+ content: {
6374
+ "application/json": components["schemas"]["ErrorEnvelope"];
6375
+ };
6376
+ };
6377
+ /** @description Internal server error */
6378
+ 500: {
6379
+ headers: {
6380
+ [name: string]: unknown;
6381
+ };
6382
+ content: {
6383
+ "application/json": components["schemas"]["ErrorEnvelope"];
6384
+ };
6385
+ };
6386
+ };
6387
+ };
6388
+ delete?: never;
6389
+ options?: never;
6390
+ head?: never;
6391
+ patch?: never;
6392
+ trace?: never;
6393
+ };
6394
+ "/v1/trial/init": {
6395
+ parameters: {
6396
+ query?: never;
6397
+ header?: never;
6398
+ path?: never;
6399
+ cookie?: never;
6400
+ };
6401
+ get?: never;
6402
+ put?: never;
6403
+ /**
6404
+ * Start a trial session
6405
+ * @description Create a pairing-pending trial session owned by the caller (dashboard JWT for direct self-serve, or an API key for wholesale/SDK). Returns a one-time pairing token the partner embeds in their onboarding UI; the recipient claims it by sending an inbound message that links them to a pool line. Direct accounts may hold only one live trial at a time.
6406
+ */
6407
+ post: {
6408
+ parameters: {
6409
+ query?: never;
6410
+ header?: never;
6411
+ path?: never;
6412
+ cookie?: never;
6413
+ };
6414
+ requestBody?: {
6415
+ content: {
6416
+ "application/json": components["schemas"]["TrialInitBody"];
6417
+ };
6418
+ };
6419
+ responses: {
6420
+ /** @description Trial session created — pairing pending */
6421
+ 201: {
6422
+ headers: {
6423
+ [name: string]: unknown;
6424
+ };
6425
+ content: {
6426
+ "application/json": components["schemas"]["TrialInitEnvelope"];
6427
+ };
6428
+ };
6429
+ /** @description Bad request — validation failed */
6430
+ 400: {
6431
+ headers: {
6432
+ [name: string]: unknown;
6433
+ };
6434
+ content: {
6435
+ "application/json": components["schemas"]["ErrorEnvelope"];
6436
+ };
6437
+ };
6438
+ /** @description Unauthenticated — missing or invalid bearer token */
6439
+ 401: {
6440
+ headers: {
6441
+ [name: string]: unknown;
6442
+ };
6443
+ content: {
6444
+ "application/json": components["schemas"]["ErrorEnvelope"];
6445
+ };
6446
+ };
6447
+ /** @description Conflict — concurrent or terminal state */
6448
+ 409: {
6449
+ headers: {
6450
+ [name: string]: unknown;
6451
+ };
6452
+ content: {
6453
+ "application/json": components["schemas"]["ErrorEnvelope"];
6454
+ };
6455
+ };
6456
+ /** @description Too many requests — rate limit exceeded */
6457
+ 429: {
6458
+ headers: {
6459
+ [name: string]: unknown;
6460
+ };
6461
+ content: {
6462
+ "application/json": components["schemas"]["ErrorEnvelope"];
6463
+ };
6464
+ };
6465
+ /** @description Internal server error */
6466
+ 500: {
6467
+ headers: {
6468
+ [name: string]: unknown;
6469
+ };
6470
+ content: {
6471
+ "application/json": components["schemas"]["ErrorEnvelope"];
6472
+ };
6473
+ };
6474
+ };
6475
+ };
6476
+ delete?: never;
6477
+ options?: never;
6478
+ head?: never;
6479
+ patch?: never;
6480
+ trace?: never;
6481
+ };
6482
+ "/v1/trial/status": {
6483
+ parameters: {
6484
+ query?: never;
6485
+ header?: never;
6486
+ path?: never;
6487
+ cookie?: never;
6488
+ };
6489
+ /**
6490
+ * Check trial pairing status
6491
+ * @description Token-keyed public lookup. Returns the current trial state — `pairing_pending` while waiting for the recipient's first inbound, `paired` once linked, `expired` after the pairing window. On first poll after `paired` the response also reveals the `trial_api_key` (`isnap_test_*`) for the partner to embed.
6492
+ */
6493
+ get: {
6494
+ parameters: {
6495
+ query: {
6496
+ token: string;
6497
+ };
6498
+ header?: never;
6499
+ path?: never;
6500
+ cookie?: never;
6501
+ };
6502
+ requestBody?: never;
6503
+ responses: {
6504
+ /** @description Current trial status */
6505
+ 200: {
6506
+ headers: {
6507
+ [name: string]: unknown;
6508
+ };
6509
+ content: {
6510
+ "application/json": components["schemas"]["TrialStatusEnvelope"];
6511
+ };
6512
+ };
6513
+ /** @description Bad request — validation failed */
6514
+ 400: {
6515
+ headers: {
6516
+ [name: string]: unknown;
6517
+ };
6518
+ content: {
6519
+ "application/json": components["schemas"]["ErrorEnvelope"];
6520
+ };
6521
+ };
6522
+ /** @description Not found */
6523
+ 404: {
6524
+ headers: {
6525
+ [name: string]: unknown;
6526
+ };
6527
+ content: {
6528
+ "application/json": components["schemas"]["ErrorEnvelope"];
6529
+ };
6530
+ };
6531
+ /** @description Too many requests — rate limit exceeded */
6532
+ 429: {
6533
+ headers: {
6534
+ [name: string]: unknown;
6535
+ };
6536
+ content: {
6537
+ "application/json": components["schemas"]["ErrorEnvelope"];
6538
+ };
6539
+ };
6540
+ /** @description Internal server error */
6541
+ 500: {
6542
+ headers: {
6543
+ [name: string]: unknown;
6544
+ };
6545
+ content: {
6546
+ "application/json": components["schemas"]["ErrorEnvelope"];
6547
+ };
6548
+ };
6549
+ };
6550
+ };
6551
+ put?: never;
6552
+ post?: never;
6553
+ delete?: never;
6554
+ options?: never;
6555
+ head?: never;
6556
+ patch?: never;
6557
+ trace?: never;
6558
+ };
6559
+ "/v1/trial/activity": {
6560
+ parameters: {
6561
+ query?: never;
6562
+ header?: never;
6563
+ path?: never;
6564
+ cookie?: never;
6565
+ };
6566
+ /**
6567
+ * Get trial activity counters
6568
+ * @description Trial-key-only snapshot of the dormancy and outbound-without-inbound counters. Partner dashboards poll this to render the trial's live state ("3/10 sends since last inbound", dormancy timer remaining).
6569
+ */
6570
+ get: {
6571
+ parameters: {
6572
+ query?: never;
6573
+ header?: never;
6574
+ path?: never;
6575
+ cookie?: never;
6576
+ };
6577
+ requestBody?: never;
6578
+ responses: {
6579
+ /** @description Trial activity snapshot */
6580
+ 200: {
6581
+ headers: {
6582
+ [name: string]: unknown;
6583
+ };
6584
+ content: {
6585
+ "application/json": components["schemas"]["TrialActivityEnvelope"];
6586
+ };
6587
+ };
6588
+ /** @description Bad request — validation failed */
6589
+ 400: {
6590
+ headers: {
6591
+ [name: string]: unknown;
6592
+ };
6593
+ content: {
6594
+ "application/json": components["schemas"]["ErrorEnvelope"];
6595
+ };
6596
+ };
6597
+ /** @description Unauthenticated — missing or invalid bearer token */
6598
+ 401: {
5412
6599
  headers: {
5413
6600
  [name: string]: unknown;
5414
6601
  };
@@ -5436,13 +6623,15 @@ export interface paths {
5436
6623
  };
5437
6624
  };
5438
6625
  };
6626
+ put?: never;
6627
+ post?: never;
5439
6628
  delete?: never;
5440
6629
  options?: never;
5441
6630
  head?: never;
5442
6631
  patch?: never;
5443
6632
  trace?: never;
5444
6633
  };
5445
- "/v1/trial/init": {
6634
+ "/v1/trial/{trial_id}/revoke": {
5446
6635
  parameters: {
5447
6636
  query?: never;
5448
6637
  header?: never;
@@ -5452,29 +6641,32 @@ export interface paths {
5452
6641
  get?: never;
5453
6642
  put?: never;
5454
6643
  /**
5455
- * Start a trial session
5456
- * @description Create a pairing-pending trial session owned by the caller (dashboard JWT for direct self-serve, or an API key for wholesale/SDK). Returns a one-time pairing token the partner embeds in their onboarding UI; the recipient claims it by sending an inbound message that links them to a pool line. Direct accounts may hold only one live trial at a time.
6644
+ * Revoke a trial session
6645
+ * @description Terminate a trial owned by the caller, freeing its pool-line slot (the same handle can immediately start a fresh trial) and invalidating its trial API key. Idempotent — revoking an already-revoked trial returns `already_revoked: true` with a 200. A trial the caller does not own returns 404 (no existence leak). Per-partner rebind policy (WHA-1642) applies ONLY to `reason: rebind` (`partner`/`admin` always pass): a partner with rebind disabled gets 403 `rebind_not_allowed`, and one over its daily rebind cap gets 429 `rebind_cap_reached` carrying `retry_at`.
5457
6646
  */
5458
6647
  post: {
5459
6648
  parameters: {
5460
6649
  query?: never;
5461
6650
  header?: never;
5462
- path?: never;
6651
+ path: {
6652
+ /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
6653
+ trial_id: string;
6654
+ };
5463
6655
  cookie?: never;
5464
6656
  };
5465
6657
  requestBody?: {
5466
6658
  content: {
5467
- "application/json": components["schemas"]["TrialInitBody"];
6659
+ "application/json": components["schemas"]["TrialRevokeBody"];
5468
6660
  };
5469
6661
  };
5470
6662
  responses: {
5471
- /** @description Trial session created — pairing pending */
5472
- 201: {
6663
+ /** @description Trial revoked (idempotent) */
6664
+ 200: {
5473
6665
  headers: {
5474
6666
  [name: string]: unknown;
5475
6667
  };
5476
6668
  content: {
5477
- "application/json": components["schemas"]["TrialInitEnvelope"];
6669
+ "application/json": components["schemas"]["TrialRevokeEnvelope"];
5478
6670
  };
5479
6671
  };
5480
6672
  /** @description Bad request — validation failed */
@@ -5495,8 +6687,26 @@ export interface paths {
5495
6687
  "application/json": components["schemas"]["ErrorEnvelope"];
5496
6688
  };
5497
6689
  };
5498
- /** @description Conflict — concurrent or terminal state */
5499
- 409: {
6690
+ /** @description Forbidden — caller authenticated but not allowed */
6691
+ 403: {
6692
+ headers: {
6693
+ [name: string]: unknown;
6694
+ };
6695
+ content: {
6696
+ "application/json": components["schemas"]["ErrorEnvelope"];
6697
+ };
6698
+ };
6699
+ /** @description Not found */
6700
+ 404: {
6701
+ headers: {
6702
+ [name: string]: unknown;
6703
+ };
6704
+ content: {
6705
+ "application/json": components["schemas"]["ErrorEnvelope"];
6706
+ };
6707
+ };
6708
+ /** @description Unprocessable — domain rule violation */
6709
+ 422: {
5500
6710
  headers: {
5501
6711
  [name: string]: unknown;
5502
6712
  };
@@ -5530,35 +6740,39 @@ export interface paths {
5530
6740
  patch?: never;
5531
6741
  trace?: never;
5532
6742
  };
5533
- "/v1/trial/status": {
6743
+ "/v1/byod/pair": {
5534
6744
  parameters: {
5535
6745
  query?: never;
5536
6746
  header?: never;
5537
6747
  path?: never;
5538
6748
  cookie?: never;
5539
6749
  };
6750
+ get?: never;
6751
+ put?: never;
5540
6752
  /**
5541
- * Check trial pairing status
5542
- * @description Token-keyed public lookup. Returns the current trial state — `pairing_pending` while waiting for the recipient's first inbound, `paired` once linked, `expired` after the pairing window. On first poll after `paired` the response also reveals the `trial_api_key` (`isnap_test_*`) for the partner to embed.
6753
+ * Start a BYOD pairing flow
6754
+ * @description Mint a one-time activation code plus the stable per-platform bridge-installer download links for a customer-owned Mac+iPhone or Android device. The customer runs the bridge, enters the code, and the device-side `POST /device/activate` consumes it to mint a `byod_imessage` or `byod_android` line — the platform is derived there from what the bridge reports.
5543
6755
  */
5544
- get: {
6756
+ post: {
5545
6757
  parameters: {
5546
- query: {
5547
- token: string;
5548
- };
6758
+ query?: never;
5549
6759
  header?: never;
5550
6760
  path?: never;
5551
6761
  cookie?: never;
5552
6762
  };
5553
- requestBody?: never;
6763
+ requestBody?: {
6764
+ content: {
6765
+ "application/json": components["schemas"]["ByodPairBody"];
6766
+ };
6767
+ };
5554
6768
  responses: {
5555
- /** @description Current trial status */
5556
- 200: {
6769
+ /** @description Activation code + bridge download URL */
6770
+ 201: {
5557
6771
  headers: {
5558
6772
  [name: string]: unknown;
5559
6773
  };
5560
6774
  content: {
5561
- "application/json": components["schemas"]["TrialStatusEnvelope"];
6775
+ "application/json": components["schemas"]["ByodPairEnvelope"];
5562
6776
  };
5563
6777
  };
5564
6778
  /** @description Bad request — validation failed */
@@ -5570,6 +6784,15 @@ export interface paths {
5570
6784
  "application/json": components["schemas"]["ErrorEnvelope"];
5571
6785
  };
5572
6786
  };
6787
+ /** @description Unauthenticated — missing or invalid bearer token */
6788
+ 401: {
6789
+ headers: {
6790
+ [name: string]: unknown;
6791
+ };
6792
+ content: {
6793
+ "application/json": components["schemas"]["ErrorEnvelope"];
6794
+ };
6795
+ };
5573
6796
  /** @description Not found */
5574
6797
  404: {
5575
6798
  headers: {
@@ -5599,26 +6822,26 @@ export interface paths {
5599
6822
  };
5600
6823
  };
5601
6824
  };
5602
- put?: never;
5603
- post?: never;
5604
6825
  delete?: never;
5605
6826
  options?: never;
5606
6827
  head?: never;
5607
6828
  patch?: never;
5608
6829
  trace?: never;
5609
6830
  };
5610
- "/v1/trial/activity": {
6831
+ "/v1/macos-sessions/rent": {
5611
6832
  parameters: {
5612
6833
  query?: never;
5613
6834
  header?: never;
5614
6835
  path?: never;
5615
6836
  cookie?: never;
5616
6837
  };
6838
+ get?: never;
6839
+ put?: never;
5617
6840
  /**
5618
- * Get trial activity counters
5619
- * @description Trial-key-only snapshot of the dormancy and outbound-without-inbound counters. Partner dashboards poll this to render the trial's live state ("3/10 sends since last inbound", dormancy timer remaining).
6841
+ * Rent a hosted macOS session
6842
+ * @description Take one hosted macOS session out of iSnap stock on the calling wholesale partner’s account. The tenancy starts immediately and `anniversary_at` is stamped with the day it was taken — that day of the month is what the session bills on, clamped to the last day of shorter months. Returns `409 no_macos_session_in_stock` when no confirmed, unrented session sits on a delivered Mac; iSnap provisions one and attaches it, and no request needs to be repeated. Direct customers do not use this verb — they buy sessions through `POST /v1/billing/mac-checkout` and are refused here with `403 not_a_wholesale_partner`.
5620
6843
  */
5621
- get: {
6844
+ post: {
5622
6845
  parameters: {
5623
6846
  query?: never;
5624
6847
  header?: never;
@@ -5627,13 +6850,13 @@ export interface paths {
5627
6850
  };
5628
6851
  requestBody?: never;
5629
6852
  responses: {
5630
- /** @description Trial activity snapshot */
5631
- 200: {
6853
+ /** @description The rented session */
6854
+ 201: {
5632
6855
  headers: {
5633
6856
  [name: string]: unknown;
5634
6857
  };
5635
6858
  content: {
5636
- "application/json": components["schemas"]["TrialActivityEnvelope"];
6859
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5637
6860
  };
5638
6861
  };
5639
6862
  /** @description Bad request — validation failed */
@@ -5654,6 +6877,24 @@ export interface paths {
5654
6877
  "application/json": components["schemas"]["ErrorEnvelope"];
5655
6878
  };
5656
6879
  };
6880
+ /** @description Forbidden — caller authenticated but not allowed */
6881
+ 403: {
6882
+ headers: {
6883
+ [name: string]: unknown;
6884
+ };
6885
+ content: {
6886
+ "application/json": components["schemas"]["ErrorEnvelope"];
6887
+ };
6888
+ };
6889
+ /** @description Conflict — concurrent or terminal state */
6890
+ 409: {
6891
+ headers: {
6892
+ [name: string]: unknown;
6893
+ };
6894
+ content: {
6895
+ "application/json": components["schemas"]["ErrorEnvelope"];
6896
+ };
6897
+ };
5657
6898
  /** @description Too many requests — rate limit exceeded */
5658
6899
  429: {
5659
6900
  headers: {
@@ -5674,15 +6915,13 @@ export interface paths {
5674
6915
  };
5675
6916
  };
5676
6917
  };
5677
- put?: never;
5678
- post?: never;
5679
6918
  delete?: never;
5680
6919
  options?: never;
5681
6920
  head?: never;
5682
6921
  patch?: never;
5683
6922
  trace?: never;
5684
6923
  };
5685
- "/v1/trial/{trial_id}/revoke": {
6924
+ "/v1/macos-sessions/{id}/cancel": {
5686
6925
  parameters: {
5687
6926
  query?: never;
5688
6927
  header?: never;
@@ -5692,32 +6931,27 @@ export interface paths {
5692
6931
  get?: never;
5693
6932
  put?: never;
5694
6933
  /**
5695
- * Revoke a trial session
5696
- * @description Terminate a trial owned by the caller, freeing its pool-line slot (the same handle can immediately start a fresh trial) and invalidating its trial API key. Idempotent — revoking an already-revoked trial returns `already_revoked: true` with a 200. A trial the caller does not own returns 404 (no existence leak). Per-partner rebind policy (WHA-1642) applies ONLY to `reason: rebind` (`partner`/`admin` always pass): a partner with rebind disabled gets 403 `rebind_not_allowed`, and one over its daily rebind cap gets 429 `rebind_cap_reached` carrying `retry_at`.
6934
+ * Cancel a rented macOS session
6935
+ * @description End the tenancy at its next anniversary. The session stays usable and keeps billing until `cancel_at`, which is the next occurrence of `anniversary_at` strictly after the cancellation — so cancelling ON the anniversary buys the month that starts that day, never zero days. Idempotent: a second call returns the `cancel_at` the first one set rather than pushing it a month further out. Only the renting partner may cancel; every other caller gets `404`.
5697
6936
  */
5698
6937
  post: {
5699
6938
  parameters: {
5700
6939
  query?: never;
5701
6940
  header?: never;
5702
6941
  path: {
5703
- /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
5704
- trial_id: string;
6942
+ id: string;
5705
6943
  };
5706
6944
  cookie?: never;
5707
6945
  };
5708
- requestBody?: {
5709
- content: {
5710
- "application/json": components["schemas"]["TrialRevokeBody"];
5711
- };
5712
- };
6946
+ requestBody?: never;
5713
6947
  responses: {
5714
- /** @description Trial revoked (idempotent) */
6948
+ /** @description The cancelled session */
5715
6949
  200: {
5716
6950
  headers: {
5717
6951
  [name: string]: unknown;
5718
6952
  };
5719
6953
  content: {
5720
- "application/json": components["schemas"]["TrialRevokeEnvelope"];
6954
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5721
6955
  };
5722
6956
  };
5723
6957
  /** @description Bad request — validation failed */
@@ -5738,15 +6972,6 @@ export interface paths {
5738
6972
  "application/json": components["schemas"]["ErrorEnvelope"];
5739
6973
  };
5740
6974
  };
5741
- /** @description Forbidden — caller authenticated but not allowed */
5742
- 403: {
5743
- headers: {
5744
- [name: string]: unknown;
5745
- };
5746
- content: {
5747
- "application/json": components["schemas"]["ErrorEnvelope"];
5748
- };
5749
- };
5750
6975
  /** @description Not found */
5751
6976
  404: {
5752
6977
  headers: {
@@ -5756,8 +6981,8 @@ export interface paths {
5756
6981
  "application/json": components["schemas"]["ErrorEnvelope"];
5757
6982
  };
5758
6983
  };
5759
- /** @description Unprocessable — domain rule violation */
5760
- 422: {
6984
+ /** @description Conflict — concurrent or terminal state */
6985
+ 409: {
5761
6986
  headers: {
5762
6987
  [name: string]: unknown;
5763
6988
  };
@@ -5791,7 +7016,7 @@ export interface paths {
5791
7016
  patch?: never;
5792
7017
  trace?: never;
5793
7018
  };
5794
- "/v1/byod/pair": {
7019
+ "/v1/macos-sessions/{id}/uncancel": {
5795
7020
  parameters: {
5796
7021
  query?: never;
5797
7022
  header?: never;
@@ -5801,29 +7026,27 @@ export interface paths {
5801
7026
  get?: never;
5802
7027
  put?: never;
5803
7028
  /**
5804
- * Start a BYOD pairing flow
5805
- * @description Mint a one-time activation code plus the stable per-platform bridge-installer download links for a customer-owned Mac+iPhone or Android device. The customer runs the bridge, enters the code, and the device-side `POST /device/activate` consumes it to mint a `byod_imessage` or `byod_android` line — the platform is derived there from what the bridge reports.
7029
+ * Keep a cancelled macOS session
7030
+ * @description Withdraw a pending cancellation, clearing `cancel_at` and leaving `anniversary_at` untouched — the billing day does not move. Idempotent on a session that is not cancelled. Only possible before the release runs: once `cancel_at` has passed the paid month is over and this answers `409 macos_session_cancellation_already_due` — the release is hourly, so there is a window in which the date has passed and the session is still rented, and un-cancelling inside it would hold a seat the monthly tally has already stopped billing. After the release has run the tenancy is gone and this returns `404`; the session must be rented again.
5806
7031
  */
5807
7032
  post: {
5808
7033
  parameters: {
5809
7034
  query?: never;
5810
7035
  header?: never;
5811
- path?: never;
5812
- cookie?: never;
5813
- };
5814
- requestBody?: {
5815
- content: {
5816
- "application/json": components["schemas"]["ByodPairBody"];
7036
+ path: {
7037
+ id: string;
5817
7038
  };
7039
+ cookie?: never;
5818
7040
  };
7041
+ requestBody?: never;
5819
7042
  responses: {
5820
- /** @description Activation code + bridge download URL */
5821
- 201: {
7043
+ /** @description The session, no longer cancelled */
7044
+ 200: {
5822
7045
  headers: {
5823
7046
  [name: string]: unknown;
5824
7047
  };
5825
7048
  content: {
5826
- "application/json": components["schemas"]["ByodPairEnvelope"];
7049
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5827
7050
  };
5828
7051
  };
5829
7052
  /** @description Bad request — validation failed */
@@ -5853,6 +7076,15 @@ export interface paths {
5853
7076
  "application/json": components["schemas"]["ErrorEnvelope"];
5854
7077
  };
5855
7078
  };
7079
+ /** @description Conflict — concurrent or terminal state */
7080
+ 409: {
7081
+ headers: {
7082
+ [name: string]: unknown;
7083
+ };
7084
+ content: {
7085
+ "application/json": components["schemas"]["ErrorEnvelope"];
7086
+ };
7087
+ };
5856
7088
  /** @description Too many requests — rate limit exceeded */
5857
7089
  429: {
5858
7090
  headers: {
@@ -5888,7 +7120,7 @@ export interface paths {
5888
7120
  };
5889
7121
  /**
5890
7122
  * Get the app/bridge download manifest (public)
5891
- * @description Per-platform installer URL, advertised bridge version, and availability. Requires no pairing and no auth — use it for setup guides, "add a device" flows, reinstalls, and update prompts. The URLs are the same stable 302-redirect endpoints `POST /v1/byod/pair` returns in `download_urls`, and `version` is the same string it returns as `bridge_version`; both surfaces derive from one builder so they cannot drift.
7123
+ * @description Per-platform installer URL, published version, and availability. Requires no pairing and no auth — use it for setup guides, "add a device" flows, reinstalls, and update prompts. The URLs are the same stable 302-redirect endpoints `POST /v1/byod/pair` returns in `download_urls`, built by one shared builder so they cannot drift. `version` is read from the release feed beside the artifact the redirect resolves to, and is `null` when that feed publishes nothing — never a configured placeholder.
5892
7124
  */
5893
7125
  get: {
5894
7126
  parameters: {
@@ -5996,7 +7228,7 @@ export interface components {
5996
7228
  state_name: string | null;
5997
7229
  /** @enum {string} */
5998
7230
  billing_tier: "rental_iphone" | "rental_android" | "byod_imessage" | "byod_android" | "shared_pool" | "shared_imessage";
5999
- /** @description True when the customer owns the underlying hardware (BYOD billing tiers). BYOD lines expose the full line-health surface (offline/degraded events, GET /v1/lines/{id}/health). Rental lines (false) only emit durable, actionable events to keep customers out of transient infrastructure noise they cannot act on. */
7231
+ /** @description True when the customer owns the underlying hardware (BYOD billing tiers). BYOD lines expose the full line-health surface: GET /v1/lines/{id}/health, the immediate line.offline/line.connected with their diagnostic fields, and the device-health-flux events line.degraded and line.capability_changed. Rental lines (false) are told about DURABLE connectivity changes only, and with NO diagnostic detail by design — line.disconnected always; line.offline once an outage has lasted ≥15 minutes (durable:true, carrying only line_id and detected_at — present it as "line temporarily unavailable, being resolved by the provider"); line.connected on hand-over and on recovery from a surfaced outage — never a transient blip, and never the BYOD-only device-health-flux events they cannot act on. */
6000
7232
  byod: boolean;
6001
7233
  /** @description True when the line cannot INITIATE a conversation: the contact must send the first message, after which the exchange is unrestricted in both directions. It is NOT receive-only — the line replies, reacts and sends attachments normally once contacted. Messaging a handle that has never contacted the line is rejected with 403 outbound_first_forbidden, per recipient and with no time limit once unlocked. Cheaper than the outbound tier; always false on BYOD/pool lines. */
6002
7234
  inbound_only: boolean;
@@ -6010,6 +7242,19 @@ export interface components {
6010
7242
  stripe_subscription_id: string | null;
6011
7243
  activated_at: string | null;
6012
7244
  expires_at: string | null;
7245
+ /** @description The day you took this line, and therefore the day of the month it renews on: billed once per month at this instant, never prorated. Stamped when the line becomes yours; `null` means the line has no per-line anniversary — a direct customer's line renews on its Stripe subscription's own anchor, and a pool line is billed to nobody. Set it explicitly with POST /v1/lines/{id}/anniversary for a line you migrated from your own system, whose real start date iSnap cannot know. */
7246
+ anniversary_at: string | null;
7247
+ /** @description The anniversary occurrence at which iSnap will END this line, set by POST /v1/lines/{id}/cancel and cleared by POST /v1/lines/{id}/uncancel. The line stays fully active and usable until this instant — the month is already paid for and is never prorated — and iSnap terminates it itself when the date arrives. `null` means the line is not cancelled. It survives the termination it schedules, so a `cancelled` line still says which date ended it. */
7248
+ cancel_at: string | null;
7249
+ /** @description The billing anchor you stated for this line — the date the customer who pays for it is billed on. Every line that customer holds moves on this same date, so their period and your invoice advance together. `null` means you stated nothing, NOT that the line has no billing date: it then falls back to the activation instant, which is how every line was billed before anchors existed. Set it at provisioning (`POST /v1/pre-orders`, `POST /v1/lines/{id}/activate`) or change it later with `PATCH /v1/lines/{id}` — a change takes effect at the NEXT anchor and never rewrites a period already billed. */
7250
+ billing_anchor_at: string | null;
7251
+ /** @description The next instant this line will be billed on, derived from the anchor above (or from the activation instant when you stated none) and clamped to the last day of a shorter month — a line anchored on the 31st bills on the 28th in February and on the 31st again in March, the clamp never sticks. At that instant the line is charged one full month at the product it carries then; at the FIRST occurrence after activation it is also credited for the days the activation month had already paid for past it, at the activation rate. `null` when the line will not be billed again: never activated, not in a billable status, or cancelled on or before that occurrence. */
7252
+ next_anchor_at: string | null;
7253
+ /** @description The variant change scheduled for this line's next billing anchor, or `null` when none is pending. `inbound_only` is the value the line WILL carry — the live value stays in the top-level `inbound_only` field until `effective_at` arrives, because the line keeps serving the product it is being billed for until then. Schedule one with POST /v1/lines/{id}/variant and `effective: 'next_anchor'`; cancel it with DELETE /v1/lines/{id}/variant. It is cleared when the change is applied, when it is cancelled, and when the line leaves its owner. */
7254
+ pending_variant: {
7255
+ inbound_only: boolean;
7256
+ effective_at: string;
7257
+ } | null;
6013
7258
  created_at: string;
6014
7259
  metadata: {
6015
7260
  [key: string]: unknown;
@@ -6020,6 +7265,31 @@ export interface components {
6020
7265
  shared: boolean;
6021
7266
  fulfillment_status?: string | null;
6022
7267
  };
7268
+ MacosSession: {
7269
+ /** @example mcs_7Kd2Nq8Rb4Xw1Ty6Ue9Ao3Ph5 */
7270
+ id: string;
7271
+ /** @description Whether this session is currently rented by the calling partner. `false` once it has been released back to iSnap stock. */
7272
+ rented: boolean;
7273
+ /**
7274
+ * Format: date-time
7275
+ * @description The day the session was taken, and the day of the month it bills on. Null once the session is released. A day-of-month past the end of a shorter month bills on that month’s last day.
7276
+ * @example 2026-09-07T14:32:00.000Z
7277
+ */
7278
+ anniversary_at: string | null;
7279
+ /**
7280
+ * Format: date-time
7281
+ * @description When the tenancy ends — always the next occurrence of `anniversary_at` after the cancellation. Null when the session is not cancelled. The session stays usable and keeps billing until this instant.
7282
+ * @example 2026-10-07T14:32:00.000Z
7283
+ */
7284
+ cancel_at: string | null;
7285
+ /**
7286
+ * @description `pending_provisioning` while the Mac hosting the session has not been delivered yet; `created` once it has.
7287
+ * @enum {string}
7288
+ */
7289
+ status: "created" | "pending_provisioning";
7290
+ /** Format: date-time */
7291
+ created_at: string;
7292
+ };
6023
7293
  ApiKey: {
6024
7294
  /** Format: uuid */
6025
7295
  id: string;
@@ -6066,16 +7336,54 @@ export interface components {
6066
7336
  };
6067
7337
  ActivateLine: {
6068
7338
  inbound_only?: boolean;
7339
+ /**
7340
+ * Format: date-time
7341
+ * @description The billing anchor of the customer who pays for this line (ISO-8601, UTC) — the ONE date it is billed on. Its day of the month becomes the line’s billing day, clamped to the last day of a shorter month; the time of day is kept, because the anchor is an instant and not a calendar day. State the date your own customer is billed on and every line they hold moves together: at each occurrence the line is charged one full month, and at the FIRST occurrence after activation it is credited for the days the activation month had already paid for past it, at the activation rate. Omit it and the line is billed on its activation instant, which is the behaviour of every line provisioned before anchors existed. Refused (400 `billing_anchor_too_far`) more than 31 days after the activation — that would make the first period longer than a month.
7342
+ * @example 2026-09-11T09:15:00Z
7343
+ */
7344
+ billing_anchor_at?: string;
6069
7345
  };
6070
7346
  TransferOwnership: {
6071
7347
  /** Format: uuid */
6072
7348
  to_user_id: string;
6073
7349
  preserve_device_session?: boolean;
6074
7350
  };
7351
+ SetLineVariant: {
7352
+ /** @description True for the INBOUND product: the line may then only message a handle that has messaged it FIRST (`outbound_first_forbidden`), and once that handle has, the exchange is unrestricted in both directions. It is NOT receive-only. False for the two-way product. Rental lines only. */
7353
+ inbound_only: boolean;
7354
+ /**
7355
+ * @description WHEN the variant moves. `now` (the default, and the only behaviour before this field existed) writes the variant immediately and it applies to the very next send. `next_anchor` stores the change against the line's next billing anchor and applies nothing today: `inbound_only` keeps its current value, the line keeps serving the product it is being billed for, and `pending_variant` on the line read carries what will change and when. Use `next_anchor` for a DOWNGRADE, so the customer keeps the two-way product through the month they have already paid for; use `now` for an upgrade. A scheduled change is cancelled with DELETE /v1/lines/{id}/variant, and an immediate change does NOT cancel one — the two are separate acts.
7356
+ * @enum {string}
7357
+ */
7358
+ effective?: "now" | "next_anchor";
7359
+ /**
7360
+ * Format: date-time
7361
+ * @description Only meaningful with `effective: "next_anchor"`. The earliest instant the change may take effect (ISO-8601, UTC); defaults to now. iSnap schedules it at the FIRST anchor occurrence at or after this instant, with the anchor's own day-of-month clamp applied — so state your customer's paid-period end and let iSnap resolve the occurrence rather than computing one yourself. For a monthly customer that is the next anchor, which is the default; for a quarterly or annual customer it is the occurrence three or twelve months out, and the line keeps its two-way capability for the whole period they have paid for. The resolved instant comes back as `pending_variant.effective_at`. An instant in the past is refused with 400 `not_before_in_the_past`.
7362
+ * @example 2027-08-11T14:30:00Z
7363
+ */
7364
+ not_before?: string;
7365
+ };
7366
+ TerminateLine: {
7367
+ reason?: string;
7368
+ };
7369
+ SetLineAnniversary: {
7370
+ /**
7371
+ * Format: date-time
7372
+ * @description The instant you took this line (ISO-8601, UTC). Its day of the month becomes the line’s renewal day and the date a cancellation waits for; the time of day is kept, because the anniversary is an instant and not a calendar day. Use this for a line migrated from your own system, whose real start date iSnap cannot know — for a line taken through the iSnap API it is stamped automatically and does not need stating. A BYOD line carries one too — it is on the same monthly meter — even though iSnap cannot end it for you at that date. Refused (409 `line_cancellation_pending`) while the line carries a `cancel_at`, which was computed from the anniversary you are replacing: uncancel first, set the date, cancel again.
7373
+ * @example 2026-07-02T09:15:00Z
7374
+ */
7375
+ anniversary_at: string;
7376
+ };
6075
7377
  PatchLine: {
6076
- metadata: {
7378
+ metadata?: {
6077
7379
  [key: string]: unknown;
6078
7380
  };
7381
+ /**
7382
+ * Format: date-time
7383
+ * @description The billing anchor of the customer who pays for this line (ISO-8601, UTC) — the ONE date it is billed on. Its day of the month becomes the line’s billing day, clamped to the last day of a shorter month; the time of day is kept, because the anchor is an instant and not a calendar day. State the date your own customer is billed on and every line they hold moves together: at each occurrence the line is charged one full month, and at the FIRST occurrence after activation it is credited for the days the activation month had already paid for past it, at the activation rate. Omit it and the line is billed on its activation instant, which is the behaviour of every line provisioned before anchors existed. Refused (400 `billing_anchor_too_far`) more than 31 days after the activation — that would make the first period longer than a month.
7384
+ * @example 2026-09-11T09:15:00Z
7385
+ */
7386
+ billing_anchor_at?: string | null;
6079
7387
  };
6080
7388
  QuotaEnvelope: {
6081
7389
  /** @enum {boolean} */
@@ -6096,6 +7404,14 @@ export interface components {
6096
7404
  hourly_message_limit: number;
6097
7405
  minute_messages_used: number;
6098
7406
  minute_message_limit: number;
7407
+ daily_replies_used: number;
7408
+ daily_reply_limit: number;
7409
+ minute_frees_at: string | null;
7410
+ hourly_frees_at: string | null;
7411
+ daily_total_frees_at: string | null;
7412
+ daily_new_frees_at: string | null;
7413
+ daily_known_frees_at: string | null;
7414
+ daily_reply_frees_at: string | null;
6099
7415
  buckets_warning: components["schemas"]["BucketsWarning"];
6100
7416
  };
6101
7417
  BucketsWarning: {
@@ -6135,6 +7451,8 @@ export interface components {
6135
7451
  hourly_message_limit: number;
6136
7452
  minute_messages_used: number;
6137
7453
  minute_message_limit: number;
7454
+ daily_replies_used: number;
7455
+ daily_reply_limit: number;
6138
7456
  buckets_warning: components["schemas"]["BucketsWarning"];
6139
7457
  };
6140
7458
  QueueItem: {
@@ -6148,6 +7466,7 @@ export interface components {
6148
7466
  queue_position: number;
6149
7467
  is_new_contact: boolean | null;
6150
7468
  target_activity_hour: number | null;
7469
+ timezone: string;
6151
7470
  created_at: string;
6152
7471
  };
6153
7472
  LineHealthEnvelope: {
@@ -6201,6 +7520,9 @@ export interface components {
6201
7520
  min_inter_message_seconds: number;
6202
7521
  min_inter_new_contact_seconds: number;
6203
7522
  max_queue_depth: number;
7523
+ reply_window_hours: number;
7524
+ reply_max_per_inbound: number;
7525
+ daily_reply_limit: number;
6204
7526
  timezone: string;
6205
7527
  activity_curve: {
6206
7528
  hour: number;
@@ -6213,6 +7535,9 @@ export interface components {
6213
7535
  daily_total_message_limit: components["schemas"]["LineConfigKnobBounds"];
6214
7536
  hourly_message_limit: components["schemas"]["LineConfigKnobBounds"];
6215
7537
  minute_message_limit: components["schemas"]["LineConfigKnobBounds"];
7538
+ reply_window_hours: components["schemas"]["LineConfigKnobBounds"];
7539
+ reply_max_per_inbound: components["schemas"]["LineConfigKnobBounds"];
7540
+ daily_reply_limit: components["schemas"]["LineConfigKnobBounds"];
6216
7541
  };
6217
7542
  LineConfigKnobBounds: {
6218
7543
  current: number;
@@ -6232,6 +7557,9 @@ export interface components {
6232
7557
  min_inter_message_seconds?: number;
6233
7558
  min_inter_new_contact_seconds?: number;
6234
7559
  max_queue_depth?: number;
7560
+ reply_window_hours?: number;
7561
+ reply_max_per_inbound?: number;
7562
+ daily_reply_limit?: number;
6235
7563
  timezone?: string;
6236
7564
  activity_curve?: {
6237
7565
  hour: number;
@@ -6241,20 +7569,19 @@ export interface components {
6241
7569
  PreOrder: {
6242
7570
  /** Format: uuid */
6243
7571
  id: string;
6244
- /** @enum {string} */
6245
- billing_tier: "rental_iphone" | "rental_android";
6246
7572
  preferred_area_codes: string[];
7573
+ inbound_only: boolean;
7574
+ /** @description The billing anchor you stated when placing this pre-order, carried onto the line at confirm. `null` means you stated none and the line is billed on its activation instant. */
7575
+ billing_anchor_at: string | null;
6247
7576
  status: components["schemas"]["PreOrderStatus"];
6248
- stripe_payment_intent_id: string | null;
6249
7577
  stripe_client_secret: string | null;
6250
- amount: number;
6251
7578
  estimated_fulfillment: string | null;
6252
7579
  device_status?: components["schemas"]["DeviceFulfillmentStatus"];
6253
7580
  created_at: string;
6254
7581
  updated_at: string;
6255
7582
  };
6256
7583
  /** @enum {string} */
6257
- PreOrderStatus: "draft" | "pending" | "provisioning" | "ready" | "fulfilled" | "cancelled" | "refunded";
7584
+ PreOrderStatus: "draft" | "provisioning" | "fulfilled" | "cancelled";
6258
7585
  /** @enum {string|null} */
6259
7586
  DeviceFulfillmentStatus: "awaiting_hardware" | "received" | "setup_in_progress" | "ready" | "deployed" | null;
6260
7587
  PreOrderList: {
@@ -6298,11 +7625,71 @@ export interface components {
6298
7625
  */
6299
7626
  device_count: number;
6300
7627
  /**
6301
- * @description Stripe price cadence. Annual is 10x monthly (2 months free).
7628
+ * @description Stripe price cadence. Quarterly is 2.7x monthly (10% off) and annual is 10x monthly (2 months free).
7629
+ * @example monthly
7630
+ * @enum {string}
7631
+ */
7632
+ billing_cycle: "monthly" | "quarterly" | "annual";
7633
+ };
7634
+ MacCheckoutEnvelope: {
7635
+ /** @enum {boolean} */
7636
+ success: true;
7637
+ data: components["schemas"]["MacCheckoutResponse"];
7638
+ trace_id: string;
7639
+ request_id: string;
7640
+ };
7641
+ MacCheckoutResponse: {
7642
+ /**
7643
+ * Format: uri
7644
+ * @description Hosted Stripe Checkout URL — redirect the customer here.
7645
+ */
7646
+ checkout_url: string;
7647
+ /**
7648
+ * Format: date-time
7649
+ * @description Session expiry (ISO-8601). Stripe default is 24h.
7650
+ */
7651
+ expires_at: string;
7652
+ /** @description Sessions ordered — echoed back as accepted. */
7653
+ quantity: number;
7654
+ /** @description The pack divisor in force, read from the Stripe Price’s `pack_size` metadata. Quantities must be whole multiples of it, at or above it. */
7655
+ pack_size: number;
7656
+ /** @description Recurring total per period in integer cents, from the Price’s graduated tier ladder. Excludes tax, proration and coupons. */
7657
+ estimated_total_cents: number;
7658
+ };
7659
+ MacCheckoutBody: {
7660
+ /**
7661
+ * @description Number of hosted macOS sessions to rent. Must be a whole number of packs at or above the one-pack minimum — the pack size is read from the Stripe Price and echoed as `pack_size` in the response and in the `422` error `issues[0].min`. A quantity that is not a whole pack is refused `422 mac_quantity_not_a_pack`.
7662
+ * @example 5
7663
+ */
7664
+ quantity: number;
7665
+ /**
7666
+ * @description Stripe price cadence for the session subscription.
6302
7667
  * @example monthly
6303
7668
  * @enum {string}
6304
7669
  */
6305
- billing_cycle: "monthly" | "annual";
7670
+ billing_cycle: "monthly" | "quarterly" | "annual";
7671
+ };
7672
+ SubscriptionQuantityEnvelope: {
7673
+ /** @enum {boolean} */
7674
+ success: true;
7675
+ data: components["schemas"]["SubscriptionQuantityResponse"];
7676
+ trace_id: string;
7677
+ request_id: string;
7678
+ };
7679
+ SubscriptionQuantityResponse: {
7680
+ /** @description The Stripe subscription ID (`sub_*`). */
7681
+ subscription_id: string;
7682
+ /** @description The quantity now set on the adjustable item. */
7683
+ quantity: number;
7684
+ /** @description Recurring total per period in integer cents. `null` when the item’s Price carries no computable per-period total. Excludes tax, proration and coupons. */
7685
+ estimated_total_cents: number | null;
7686
+ };
7687
+ SubscriptionQuantityBody: {
7688
+ /**
7689
+ * @description New quantity for the subscription’s single adjustable item — macOS sessions, or BYOP slots. Both products are subject to the 1:1 rule (see the `422` codes), and the macOS product additionally to the pack rule.
7690
+ * @example 10
7691
+ */
7692
+ quantity: number;
6306
7693
  };
6307
7694
  SubscriptionListEnvelope: {
6308
7695
  /** @enum {boolean} */
@@ -6401,6 +7788,7 @@ export interface components {
6401
7788
  reactions: components["schemas"]["MessageReactionV1"][];
6402
7789
  scheduled_for: string | null;
6403
7790
  schedule_reason: string | null;
7791
+ reply_lane: boolean;
6404
7792
  queue_info: {
6405
7793
  queue_position: number;
6406
7794
  is_new_contact: boolean;
@@ -6410,6 +7798,9 @@ export interface components {
6410
7798
  daily_known_contact_limit: number;
6411
7799
  daily_messages_used: number;
6412
7800
  daily_total_message_limit: number;
7801
+ reply_lane: boolean;
7802
+ daily_replies_used: number;
7803
+ daily_reply_limit: number;
6413
7804
  messages_ahead_in_queue?: number;
6414
7805
  daily_slot_of?: string;
6415
7806
  target_activity_hour?: number;
@@ -6424,6 +7815,8 @@ export interface components {
6424
7815
  delivered_at: string | null;
6425
7816
  read_at: string | null;
6426
7817
  failed_at: string | null;
7818
+ error_code: string | null;
7819
+ error_message: string | null;
6427
7820
  cancelled_at: string | null;
6428
7821
  };
6429
7822
  /** @enum {string} */
@@ -6515,7 +7908,7 @@ export interface components {
6515
7908
  * @description A concrete event type (e.g. `message.received`), a family wildcard (`line.*`), or the catch-all `*`.
6516
7909
  * @enum {string}
6517
7910
  */
6518
- WebhookEventName: "message.queued" | "message.sent" | "message.delivered" | "message.read" | "message.failed" | "message.received" | "message.scheduled" | "message.cancelled" | "message.fallback_triggered" | "reaction.added" | "reaction.received" | "reaction.removed" | "line.connected" | "line.disconnected" | "line.offline" | "line.degraded" | "line.apple_id_flagged" | "line.quota_warning" | "line.quota_exceeded" | "line.capability_changed" | "typing_indicator.started" | "typing_indicator.stopped" | "trial.linked" | "trial.dormant" | "trial.reactivated" | "trial.bind_conflict" | "trial.revoked" | "binding.released" | "pre_order.fulfilled" | "pre_order.cancelled" | "pre_order.refunded" | "webhook.test" | "*" | "message.*" | "reaction.*" | "line.*" | "typing_indicator.*" | "trial.*" | "binding.*" | "pre_order.*" | "webhook.*";
7911
+ WebhookEventName: "message.queued" | "message.sent" | "message.delivered" | "message.read" | "message.failed" | "message.received" | "message.scheduled" | "message.cancelled" | "message.fallback_triggered" | "reaction.added" | "reaction.received" | "reaction.removed" | "line.connected" | "line.disconnected" | "line.offline" | "line.degraded" | "line.apple_id_flagged" | "line.quota_warning" | "line.quota_exceeded" | "line.capability_changed" | "line.terminated" | "line.variant_changed" | "line.variant_scheduled" | "line.variant_unscheduled" | "typing_indicator.started" | "typing_indicator.stopped" | "trial.linked" | "trial.dormant" | "trial.reactivated" | "trial.bind_conflict" | "trial.revoked" | "binding.released" | "pre_order.fulfilled" | "pre_order.cancelled" | "macos_session.rented" | "macos_session.released" | "webhook.test" | "*" | "message.*" | "reaction.*" | "line.*" | "typing_indicator.*" | "trial.*" | "binding.*" | "pre_order.*" | "macos_session.*" | "webhook.*";
6519
7912
  WebhookList: {
6520
7913
  webhooks: components["schemas"]["Webhook"][];
6521
7914
  };
@@ -6663,6 +8056,17 @@ export interface components {
6663
8056
  handle: string;
6664
8057
  from_line_id?: string;
6665
8058
  };
8059
+ AttentionList: {
8060
+ entries: components["schemas"]["AttentionEntry"][];
8061
+ };
8062
+ AttentionEntry: {
8063
+ line_id: string;
8064
+ phone_number: string | null;
8065
+ ownership: string;
8066
+ since: string | null;
8067
+ reasons: string[];
8068
+ detail_withheld: boolean;
8069
+ };
6666
8070
  AreaCodesEnvelope: {
6667
8071
  /** @enum {boolean} */
6668
8072
  success: true;
@@ -6681,6 +8085,27 @@ export interface components {
6681
8085
  region: string;
6682
8086
  /** @example US */
6683
8087
  country_code: string;
8088
+ /**
8089
+ * @description A ZIP code whose geography this area code serves — the postal address to use at purchase, and the address most likely to have the carrier assign this area code. `null` when no ZIP is on record for this code: choosing the address is then a judgement call for the operator, and this catalogue may not know of an overlay covering the code either, so no regional address can be assumed to yield it.
8090
+ * @example 10019
8091
+ */
8092
+ purchase_zip: string | null;
8093
+ /**
8094
+ * @description Fallback ZIPs in the same geography, to retry with if the carrier rejects the primary. Empty when no ZIP is on record.
8095
+ * @example [
8096
+ * "10021",
8097
+ * "10023"
8098
+ * ]
8099
+ */
8100
+ alt_zips: string[];
8101
+ /**
8102
+ * @description Other area codes IN THIS CATALOGUE that overlay the same geography. Non-empty means NO address can guarantee this specific code — the carrier may assign any code in the complex. An empty list is NOT the converse guarantee: it says only that no code this endpoint can offer is known to overlay this one, and an overlay outside the catalogue may still take the number.
8103
+ * @example [
8104
+ * "646",
8105
+ * "917"
8106
+ * ]
8107
+ */
8108
+ shared_with: string[];
6684
8109
  };
6685
8110
  Country: {
6686
8111
  /** @example US */
@@ -6907,6 +8332,13 @@ export interface components {
6907
8332
  [key: string]: unknown;
6908
8333
  };
6909
8334
  };
8335
+ MacosSessionEnvelope: {
8336
+ /** @enum {boolean} */
8337
+ success: true;
8338
+ data: components["schemas"]["MacosSession"];
8339
+ trace_id: string;
8340
+ request_id: string;
8341
+ };
6910
8342
  AppManifestEnvelope: {
6911
8343
  /** @enum {boolean} */
6912
8344
  success: true;
@@ -6926,10 +8358,16 @@ export interface components {
6926
8358
  */
6927
8359
  url: string;
6928
8360
  /**
6929
- * @description Bridge version this deployment advertises — the same string `POST /v1/byod/pair` returns as `bridge_version`. Compare it against an installed version to offer an update.
6930
- * @example 2.0.1
8361
+ * @description Version currently published for this platform, read from the release feed that sits beside the artifact `url` resolves to — so the number announced and the file served cannot name different releases. `null` when that feed publishes nothing or could not be read; `version_status` says which. Never a configured placeholder: a fabricated version makes a failed install and an uninformed console indistinguishable to whoever is checking. Compare it against an installed version to offer an update.
8362
+ * @example 0.0.211
8363
+ */
8364
+ version: string | null;
8365
+ /**
8366
+ * @description Why `version` holds what it holds. `version` is non-null if and only if this is `published`, so a null is never ambiguous (WHA-2765). `none_published` — the feed answered and publishes no version (a 404, or a feed with no readable item): a real fact about the channel, render "version not published". `unreachable` — the feed could not be read (timeout, DNS failure, a body past the size cap): OUR fault or the update host's, and nobody knows what this channel publishes, so do not tell an operator the version is missing. `misconfigured` — an artifact URL is configured for the platform and no feed URL can be derived from it; someone must fix the deployment. `not_configured` — this deployment ships no artifact for the platform, which is the same fact `available: false` carries. Before this field the last three and the first were one indistinguishable `null`, so a broken appcast URL and a healthy pre-release channel rendered identically.
8367
+ * @example published
8368
+ * @enum {string}
6931
8369
  */
6932
- version: string;
8370
+ version_status: "published" | "none_published" | "unreachable" | "misconfigured" | "not_configured";
6933
8371
  /**
6934
8372
  * @description False when this deployment has no artifact configured for the platform. The entry is still returned, so a consumer can hide the affordance instead of surfacing a link that 404s on click.
6935
8373
  * @example true