@isnap/sdk 1.3.0-next.17 → 1.3.0-next.170

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.
@@ -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: {
@@ -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-only rental variant via `inbound_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). 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}/terminate": {
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
+ * Terminate a line you own
1520
+ * @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`. BYOD lines are refused (400) — that hardware is yours and is billed per device slot by `/v1/byod/*`; owned test lines are refused (403) until converted back with `/convert-from-test`.
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"]["TerminateLine"];
1534
+ };
1535
+ };
1522
1536
  responses: {
1523
- /** @description Line released */
1537
+ /** @description Line terminated (or already terminated — the call is idempotent). Returns the line at its terminal `cancelled` status. */
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: {
@@ -1597,18 +1620,20 @@ export interface paths {
1597
1620
  patch?: never;
1598
1621
  trace?: never;
1599
1622
  };
1600
- "/v1/lines/{id}/quota": {
1623
+ "/v1/lines/{id}/cancel": {
1601
1624
  parameters: {
1602
1625
  query?: never;
1603
1626
  header?: never;
1604
1627
  path?: never;
1605
1628
  cookie?: never;
1606
1629
  };
1630
+ get?: never;
1631
+ put?: never;
1607
1632
  /**
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.
1633
+ * Cancel a line at its next billing anniversary
1634
+ * @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`.
1610
1635
  */
1611
- get: {
1636
+ post: {
1612
1637
  parameters: {
1613
1638
  query?: never;
1614
1639
  header?: never;
@@ -1619,13 +1644,19 @@ export interface paths {
1619
1644
  };
1620
1645
  requestBody?: never;
1621
1646
  responses: {
1622
- /** @description 5-bucket quota snapshot */
1647
+ /** @description Cancellation scheduled (or already scheduled — the call is idempotent). Returns the line, still `active`, with `cancel_at` set. */
1623
1648
  200: {
1624
1649
  headers: {
1625
1650
  [name: string]: unknown;
1626
1651
  };
1627
1652
  content: {
1628
- "application/json": components["schemas"]["QuotaEnvelope"];
1653
+ "application/json": {
1654
+ /** @enum {boolean} */
1655
+ success: true;
1656
+ data: components["schemas"]["Line"];
1657
+ trace_id: string;
1658
+ request_id: string;
1659
+ };
1629
1660
  };
1630
1661
  };
1631
1662
  /** @description Bad request — validation failed */
@@ -1664,6 +1695,15 @@ export interface paths {
1664
1695
  "application/json": components["schemas"]["ErrorEnvelope"];
1665
1696
  };
1666
1697
  };
1698
+ /** @description Conflict — concurrent or terminal state */
1699
+ 409: {
1700
+ headers: {
1701
+ [name: string]: unknown;
1702
+ };
1703
+ content: {
1704
+ "application/json": components["schemas"]["ErrorEnvelope"];
1705
+ };
1706
+ };
1667
1707
  /** @description Too many requests — rate limit exceeded */
1668
1708
  429: {
1669
1709
  headers: {
@@ -1684,32 +1724,28 @@ export interface paths {
1684
1724
  };
1685
1725
  };
1686
1726
  };
1687
- put?: never;
1688
- post?: never;
1689
1727
  delete?: never;
1690
1728
  options?: never;
1691
1729
  head?: never;
1692
1730
  patch?: never;
1693
1731
  trace?: never;
1694
1732
  };
1695
- "/v1/lines/{id}/queue": {
1733
+ "/v1/lines/{id}/uncancel": {
1696
1734
  parameters: {
1697
1735
  query?: never;
1698
1736
  header?: never;
1699
1737
  path?: never;
1700
1738
  cookie?: never;
1701
1739
  };
1740
+ get?: never;
1741
+ put?: never;
1702
1742
  /**
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.
1743
+ * Undo a scheduled cancellation
1744
+ * @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.
1705
1745
  */
1706
- get: {
1746
+ post: {
1707
1747
  parameters: {
1708
- query?: {
1709
- limit?: number;
1710
- cursor?: string;
1711
- status?: "queued" | "scheduled" | "delivered_to_device";
1712
- };
1748
+ query?: never;
1713
1749
  header?: never;
1714
1750
  path: {
1715
1751
  id: string;
@@ -1718,13 +1754,19 @@ export interface paths {
1718
1754
  };
1719
1755
  requestBody?: never;
1720
1756
  responses: {
1721
- /** @description Queue inspection summary + paginated items */
1757
+ /** @description Cancellation cleared, or the line was not cancelled. Returns the line with `cancel_at: null`. */
1722
1758
  200: {
1723
1759
  headers: {
1724
1760
  [name: string]: unknown;
1725
1761
  };
1726
1762
  content: {
1727
- "application/json": components["schemas"]["QueueEnvelope"];
1763
+ "application/json": {
1764
+ /** @enum {boolean} */
1765
+ success: true;
1766
+ data: components["schemas"]["Line"];
1767
+ trace_id: string;
1768
+ request_id: string;
1769
+ };
1728
1770
  };
1729
1771
  };
1730
1772
  /** @description Bad request — validation failed */
@@ -1763,6 +1805,15 @@ export interface paths {
1763
1805
  "application/json": components["schemas"]["ErrorEnvelope"];
1764
1806
  };
1765
1807
  };
1808
+ /** @description Conflict — concurrent or terminal state */
1809
+ 409: {
1810
+ headers: {
1811
+ [name: string]: unknown;
1812
+ };
1813
+ content: {
1814
+ "application/json": components["schemas"]["ErrorEnvelope"];
1815
+ };
1816
+ };
1766
1817
  /** @description Too many requests — rate limit exceeded */
1767
1818
  429: {
1768
1819
  headers: {
@@ -1783,26 +1834,26 @@ export interface paths {
1783
1834
  };
1784
1835
  };
1785
1836
  };
1786
- put?: never;
1787
- post?: never;
1788
1837
  delete?: never;
1789
1838
  options?: never;
1790
1839
  head?: never;
1791
1840
  patch?: never;
1792
1841
  trace?: never;
1793
1842
  };
1794
- "/v1/lines/{id}/health": {
1843
+ "/v1/lines/{id}/anniversary": {
1795
1844
  parameters: {
1796
1845
  query?: never;
1797
1846
  header?: never;
1798
1847
  path?: never;
1799
1848
  cookie?: never;
1800
1849
  };
1850
+ get?: never;
1851
+ put?: never;
1801
1852
  /**
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.
1853
+ * State the day you took this line
1854
+ * @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.
1804
1855
  */
1805
- get: {
1856
+ post: {
1806
1857
  parameters: {
1807
1858
  query?: never;
1808
1859
  header?: never;
@@ -1811,15 +1862,25 @@ export interface paths {
1811
1862
  };
1812
1863
  cookie?: never;
1813
1864
  };
1814
- requestBody?: never;
1865
+ requestBody?: {
1866
+ content: {
1867
+ "application/json": components["schemas"]["SetLineAnniversary"];
1868
+ };
1869
+ };
1815
1870
  responses: {
1816
- /** @description BYOD line health snapshot */
1871
+ /** @description Anniversary recorded. Returns the line with the new `anniversary_at`. */
1817
1872
  200: {
1818
1873
  headers: {
1819
1874
  [name: string]: unknown;
1820
1875
  };
1821
1876
  content: {
1822
- "application/json": components["schemas"]["LineHealthEnvelope"];
1877
+ "application/json": {
1878
+ /** @enum {boolean} */
1879
+ success: true;
1880
+ data: components["schemas"]["Line"];
1881
+ trace_id: string;
1882
+ request_id: string;
1883
+ };
1823
1884
  };
1824
1885
  };
1825
1886
  /** @description Bad request — validation failed */
@@ -1858,6 +1919,15 @@ export interface paths {
1858
1919
  "application/json": components["schemas"]["ErrorEnvelope"];
1859
1920
  };
1860
1921
  };
1922
+ /** @description Conflict — concurrent or terminal state */
1923
+ 409: {
1924
+ headers: {
1925
+ [name: string]: unknown;
1926
+ };
1927
+ content: {
1928
+ "application/json": components["schemas"]["ErrorEnvelope"];
1929
+ };
1930
+ };
1861
1931
  /** @description Too many requests — rate limit exceeded */
1862
1932
  429: {
1863
1933
  headers: {
@@ -1878,26 +1948,26 @@ export interface paths {
1878
1948
  };
1879
1949
  };
1880
1950
  };
1881
- put?: never;
1882
- post?: never;
1883
1951
  delete?: never;
1884
1952
  options?: never;
1885
1953
  head?: never;
1886
1954
  patch?: never;
1887
1955
  trace?: never;
1888
1956
  };
1889
- "/v1/lines/{id}/config": {
1957
+ "/v1/lines/{id}/release": {
1890
1958
  parameters: {
1891
1959
  query?: never;
1892
1960
  header?: never;
1893
1961
  path?: never;
1894
1962
  cookie?: never;
1895
1963
  };
1964
+ get?: never;
1965
+ put?: never;
1896
1966
  /**
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.
1967
+ * Release a line back to the marketplace
1968
+ * @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.
1899
1969
  */
1900
- get: {
1970
+ post: {
1901
1971
  parameters: {
1902
1972
  query?: never;
1903
1973
  header?: never;
@@ -1908,13 +1978,19 @@ export interface paths {
1908
1978
  };
1909
1979
  requestBody?: never;
1910
1980
  responses: {
1911
- /** @description Effective config (tier defaults + per-line overrides) */
1981
+ /** @description Line released */
1912
1982
  200: {
1913
1983
  headers: {
1914
1984
  [name: string]: unknown;
1915
1985
  };
1916
1986
  content: {
1917
- "application/json": components["schemas"]["LineConfigEnvelope"];
1987
+ "application/json": {
1988
+ /** @enum {boolean} */
1989
+ success: true;
1990
+ data: components["schemas"]["Line"];
1991
+ trace_id: string;
1992
+ request_id: string;
1993
+ };
1918
1994
  };
1919
1995
  };
1920
1996
  /** @description Bad request — validation failed */
@@ -1973,16 +2049,24 @@ export interface paths {
1973
2049
  };
1974
2050
  };
1975
2051
  };
1976
- put?: never;
1977
- post?: never;
1978
2052
  delete?: never;
1979
2053
  options?: never;
1980
2054
  head?: never;
2055
+ patch?: never;
2056
+ trace?: never;
2057
+ };
2058
+ "/v1/lines/{id}/quota": {
2059
+ parameters: {
2060
+ query?: never;
2061
+ header?: never;
2062
+ path?: never;
2063
+ cookie?: never;
2064
+ };
1981
2065
  /**
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.
2066
+ * Get a line's quota snapshot
2067
+ * @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.
1984
2068
  */
1985
- patch: {
2069
+ get: {
1986
2070
  parameters: {
1987
2071
  query?: never;
1988
2072
  header?: never;
@@ -1991,19 +2075,15 @@ export interface paths {
1991
2075
  };
1992
2076
  cookie?: never;
1993
2077
  };
1994
- requestBody?: {
1995
- content: {
1996
- "application/json": components["schemas"]["LineConfigPatch"];
1997
- };
1998
- };
2078
+ requestBody?: never;
1999
2079
  responses: {
2000
- /** @description Updated effective config (merged) + overridden_keys */
2080
+ /** @description 5-bucket quota snapshot */
2001
2081
  200: {
2002
2082
  headers: {
2003
2083
  [name: string]: unknown;
2004
2084
  };
2005
2085
  content: {
2006
- "application/json": components["schemas"]["LineConfigEnvelope"];
2086
+ "application/json": components["schemas"]["QuotaEnvelope"];
2007
2087
  };
2008
2088
  };
2009
2089
  /** @description Bad request — validation failed */
@@ -2062,9 +2142,15 @@ export interface paths {
2062
2142
  };
2063
2143
  };
2064
2144
  };
2145
+ put?: never;
2146
+ post?: never;
2147
+ delete?: never;
2148
+ options?: never;
2149
+ head?: never;
2150
+ patch?: never;
2065
2151
  trace?: never;
2066
2152
  };
2067
- "/v1/pre-orders": {
2153
+ "/v1/lines/{id}/queue": {
2068
2154
  parameters: {
2069
2155
  query?: never;
2070
2156
  header?: never;
@@ -2072,29 +2158,31 @@ export interface paths {
2072
2158
  cookie?: never;
2073
2159
  };
2074
2160
  /**
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.
2161
+ * Inspect a line's queued and scheduled messages
2162
+ * @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.
2077
2163
  */
2078
2164
  get: {
2079
2165
  parameters: {
2080
2166
  query?: {
2081
- status?: "draft" | "pending" | "provisioning" | "ready" | "fulfilled" | "cancelled" | "refunded";
2082
- cursor?: string;
2083
2167
  limit?: number;
2168
+ cursor?: string;
2169
+ status?: "queued" | "scheduled" | "delivered_to_device";
2084
2170
  };
2085
2171
  header?: never;
2086
- path?: never;
2172
+ path: {
2173
+ id: string;
2174
+ };
2087
2175
  cookie?: never;
2088
2176
  };
2089
2177
  requestBody?: never;
2090
2178
  responses: {
2091
- /** @description Pre-order list */
2179
+ /** @description Queue inspection — summary + paginated items */
2092
2180
  200: {
2093
2181
  headers: {
2094
2182
  [name: string]: unknown;
2095
2183
  };
2096
2184
  content: {
2097
- "application/json": components["schemas"]["PreOrderList"];
2185
+ "application/json": components["schemas"]["QueueEnvelope"];
2098
2186
  };
2099
2187
  };
2100
2188
  /** @description Bad request — validation failed */
@@ -2115,6 +2203,24 @@ export interface paths {
2115
2203
  "application/json": components["schemas"]["ErrorEnvelope"];
2116
2204
  };
2117
2205
  };
2206
+ /** @description Forbidden — caller authenticated but not allowed */
2207
+ 403: {
2208
+ headers: {
2209
+ [name: string]: unknown;
2210
+ };
2211
+ content: {
2212
+ "application/json": components["schemas"]["ErrorEnvelope"];
2213
+ };
2214
+ };
2215
+ /** @description Not found */
2216
+ 404: {
2217
+ headers: {
2218
+ [name: string]: unknown;
2219
+ };
2220
+ content: {
2221
+ "application/json": components["schemas"]["ErrorEnvelope"];
2222
+ };
2223
+ };
2118
2224
  /** @description Too many requests — rate limit exceeded */
2119
2225
  429: {
2120
2226
  headers: {
@@ -2136,43 +2242,42 @@ export interface paths {
2136
2242
  };
2137
2243
  };
2138
2244
  put?: never;
2245
+ post?: never;
2246
+ delete?: never;
2247
+ options?: never;
2248
+ head?: never;
2249
+ patch?: never;
2250
+ trace?: never;
2251
+ };
2252
+ "/v1/lines/{id}/health": {
2253
+ parameters: {
2254
+ query?: never;
2255
+ header?: never;
2256
+ path?: never;
2257
+ cookie?: never;
2258
+ };
2139
2259
  /**
2140
- * Create a pre-order (draft)
2141
- * @description Place a pre-order as a `draft` (WHA-1155). A draft has NO side effectsno 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.
2260
+ * Get a BYOD line's health snapshot
2261
+ * @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.
2142
2262
  */
2143
- post: {
2263
+ get: {
2144
2264
  parameters: {
2145
2265
  query?: never;
2146
2266
  header?: never;
2147
- path?: never;
2148
- cookie?: never;
2149
- };
2150
- requestBody?: {
2151
- content: {
2152
- "application/json": {
2153
- /** @enum {string} */
2154
- billing_tier: "rental_iphone" | "rental_android";
2155
- /** @default [] */
2156
- preferred_area_codes?: string[];
2157
- /** @default false */
2158
- inbound_only?: boolean;
2159
- };
2267
+ path: {
2268
+ id: string;
2160
2269
  };
2270
+ cookie?: never;
2161
2271
  };
2272
+ requestBody?: never;
2162
2273
  responses: {
2163
- /** @description Pre-order draft created */
2164
- 201: {
2274
+ /** @description BYOD line health snapshot */
2275
+ 200: {
2165
2276
  headers: {
2166
2277
  [name: string]: unknown;
2167
2278
  };
2168
2279
  content: {
2169
- "application/json": {
2170
- /** @enum {boolean} */
2171
- success: true;
2172
- data: components["schemas"]["PreOrder"];
2173
- trace_id: string;
2174
- request_id: string;
2175
- };
2280
+ "application/json": components["schemas"]["LineHealthEnvelope"];
2176
2281
  };
2177
2282
  };
2178
2283
  /** @description Bad request — validation failed */
@@ -2193,8 +2298,26 @@ export interface paths {
2193
2298
  "application/json": components["schemas"]["ErrorEnvelope"];
2194
2299
  };
2195
2300
  };
2196
- /** @description Too many requests rate limit exceeded */
2197
- 429: {
2301
+ /** @description Forbidden caller authenticated but not allowed */
2302
+ 403: {
2303
+ headers: {
2304
+ [name: string]: unknown;
2305
+ };
2306
+ content: {
2307
+ "application/json": components["schemas"]["ErrorEnvelope"];
2308
+ };
2309
+ };
2310
+ /** @description Not found */
2311
+ 404: {
2312
+ headers: {
2313
+ [name: string]: unknown;
2314
+ };
2315
+ content: {
2316
+ "application/json": components["schemas"]["ErrorEnvelope"];
2317
+ };
2318
+ };
2319
+ /** @description Too many requests — rate limit exceeded */
2320
+ 429: {
2198
2321
  headers: {
2199
2322
  [name: string]: unknown;
2200
2323
  };
@@ -2213,26 +2336,26 @@ export interface paths {
2213
2336
  };
2214
2337
  };
2215
2338
  };
2339
+ put?: never;
2340
+ post?: never;
2216
2341
  delete?: never;
2217
2342
  options?: never;
2218
2343
  head?: never;
2219
2344
  patch?: never;
2220
2345
  trace?: never;
2221
2346
  };
2222
- "/v1/pre-orders/{id}/confirm": {
2347
+ "/v1/lines/{id}/config": {
2223
2348
  parameters: {
2224
2349
  query?: never;
2225
2350
  header?: never;
2226
2351
  path?: never;
2227
2352
  cookie?: never;
2228
2353
  };
2229
- get?: never;
2230
- put?: never;
2231
2354
  /**
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.
2355
+ * Get a line's effective send configuration
2356
+ * @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.
2234
2357
  */
2235
- post: {
2358
+ get: {
2236
2359
  parameters: {
2237
2360
  query?: never;
2238
2361
  header?: never;
@@ -2243,19 +2366,13 @@ export interface paths {
2243
2366
  };
2244
2367
  requestBody?: never;
2245
2368
  responses: {
2246
- /** @description Pre-order confirmed (now provisioning) */
2369
+ /** @description Effective config (tier defaults + per-line overrides) */
2247
2370
  200: {
2248
2371
  headers: {
2249
2372
  [name: string]: unknown;
2250
2373
  };
2251
2374
  content: {
2252
- "application/json": {
2253
- /** @enum {boolean} */
2254
- success: true;
2255
- data: components["schemas"]["PreOrder"];
2256
- trace_id: string;
2257
- request_id: string;
2258
- };
2375
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2259
2376
  };
2260
2377
  };
2261
2378
  /** @description Bad request — validation failed */
@@ -2276,6 +2393,15 @@ export interface paths {
2276
2393
  "application/json": components["schemas"]["ErrorEnvelope"];
2277
2394
  };
2278
2395
  };
2396
+ /** @description Forbidden — caller authenticated but not allowed */
2397
+ 403: {
2398
+ headers: {
2399
+ [name: string]: unknown;
2400
+ };
2401
+ content: {
2402
+ "application/json": components["schemas"]["ErrorEnvelope"];
2403
+ };
2404
+ };
2279
2405
  /** @description Not found */
2280
2406
  404: {
2281
2407
  headers: {
@@ -2305,24 +2431,16 @@ export interface paths {
2305
2431
  };
2306
2432
  };
2307
2433
  };
2434
+ put?: never;
2435
+ post?: never;
2308
2436
  delete?: never;
2309
2437
  options?: never;
2310
2438
  head?: never;
2311
- patch?: never;
2312
- trace?: never;
2313
- };
2314
- "/v1/pre-orders/{id}": {
2315
- parameters: {
2316
- query?: never;
2317
- header?: never;
2318
- path?: never;
2319
- cookie?: never;
2320
- };
2321
2439
  /**
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.
2440
+ * Update a line's send configuration
2441
+ * @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.
2324
2442
  */
2325
- get: {
2443
+ patch: {
2326
2444
  parameters: {
2327
2445
  query?: never;
2328
2446
  header?: never;
@@ -2331,21 +2449,19 @@ export interface paths {
2331
2449
  };
2332
2450
  cookie?: never;
2333
2451
  };
2334
- requestBody?: never;
2452
+ requestBody?: {
2453
+ content: {
2454
+ "application/json": components["schemas"]["LineConfigPatch"];
2455
+ };
2456
+ };
2335
2457
  responses: {
2336
- /** @description Pre-order */
2458
+ /** @description Updated effective config (merged) + overridden_keys */
2337
2459
  200: {
2338
2460
  headers: {
2339
2461
  [name: string]: unknown;
2340
2462
  };
2341
2463
  content: {
2342
- "application/json": {
2343
- /** @enum {boolean} */
2344
- success: true;
2345
- data: components["schemas"]["PreOrder"];
2346
- trace_id: string;
2347
- request_id: string;
2348
- };
2464
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2349
2465
  };
2350
2466
  };
2351
2467
  /** @description Bad request — validation failed */
@@ -2366,6 +2482,15 @@ export interface paths {
2366
2482
  "application/json": components["schemas"]["ErrorEnvelope"];
2367
2483
  };
2368
2484
  };
2485
+ /** @description Forbidden — caller authenticated but not allowed */
2486
+ 403: {
2487
+ headers: {
2488
+ [name: string]: unknown;
2489
+ };
2490
+ content: {
2491
+ "application/json": components["schemas"]["ErrorEnvelope"];
2492
+ };
2493
+ };
2369
2494
  /** @description Not found */
2370
2495
  404: {
2371
2496
  headers: {
@@ -2395,51 +2520,39 @@ export interface paths {
2395
2520
  };
2396
2521
  };
2397
2522
  };
2398
- put?: never;
2399
- post?: never;
2400
- delete?: never;
2401
- options?: never;
2402
- head?: never;
2403
- patch?: never;
2404
2523
  trace?: never;
2405
2524
  };
2406
- "/v1/pre-orders/{id}/cancel": {
2525
+ "/v1/pre-orders": {
2407
2526
  parameters: {
2408
2527
  query?: never;
2409
2528
  header?: never;
2410
2529
  path?: never;
2411
2530
  cookie?: never;
2412
2531
  };
2413
- get?: never;
2414
- put?: never;
2415
2532
  /**
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.
2533
+ * List pre-orders
2534
+ * @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.
2418
2535
  */
2419
- post: {
2536
+ get: {
2420
2537
  parameters: {
2421
- query?: never;
2422
- header?: never;
2423
- path: {
2424
- id: string;
2538
+ query?: {
2539
+ status?: "draft" | "provisioning" | "fulfilled" | "cancelled";
2540
+ cursor?: string;
2541
+ limit?: number;
2425
2542
  };
2543
+ header?: never;
2544
+ path?: never;
2426
2545
  cookie?: never;
2427
2546
  };
2428
2547
  requestBody?: never;
2429
2548
  responses: {
2430
- /** @description Pre-order cancelled */
2549
+ /** @description Pre-order list */
2431
2550
  200: {
2432
2551
  headers: {
2433
2552
  [name: string]: unknown;
2434
2553
  };
2435
2554
  content: {
2436
- "application/json": {
2437
- /** @enum {boolean} */
2438
- success: true;
2439
- data: components["schemas"]["PreOrder"];
2440
- trace_id: string;
2441
- request_id: string;
2442
- };
2555
+ "application/json": components["schemas"]["PreOrderList"];
2443
2556
  };
2444
2557
  };
2445
2558
  /** @description Bad request — validation failed */
@@ -2460,24 +2573,6 @@ export interface paths {
2460
2573
  "application/json": components["schemas"]["ErrorEnvelope"];
2461
2574
  };
2462
2575
  };
2463
- /** @description Not found */
2464
- 404: {
2465
- headers: {
2466
- [name: string]: unknown;
2467
- };
2468
- content: {
2469
- "application/json": components["schemas"]["ErrorEnvelope"];
2470
- };
2471
- };
2472
- /** @description Conflict — concurrent or terminal state */
2473
- 409: {
2474
- headers: {
2475
- [name: string]: unknown;
2476
- };
2477
- content: {
2478
- "application/json": components["schemas"]["ErrorEnvelope"];
2479
- };
2480
- };
2481
2576
  /** @description Too many requests — rate limit exceeded */
2482
2577
  429: {
2483
2578
  headers: {
@@ -2498,24 +2593,10 @@ export interface paths {
2498
2593
  };
2499
2594
  };
2500
2595
  };
2501
- delete?: never;
2502
- options?: never;
2503
- head?: never;
2504
- patch?: never;
2505
- trace?: never;
2506
- };
2507
- "/v1/billing/checkout": {
2508
- parameters: {
2509
- query?: never;
2510
- header?: never;
2511
- path?: never;
2512
- cookie?: never;
2513
- };
2514
- get?: never;
2515
2596
  put?: never;
2516
2597
  /**
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.
2598
+ * Create a pre-order (draft)
2599
+ * @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.
2519
2600
  */
2520
2601
  post: {
2521
2602
  parameters: {
@@ -2527,27 +2608,29 @@ export interface paths {
2527
2608
  requestBody?: {
2528
2609
  content: {
2529
2610
  "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 Pick the inbound-only rental price variant (WHA-1153). 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
- */
2611
+ /** @default [] */
2612
+ preferred_area_codes?: string[];
2613
+ /** @default false */
2539
2614
  inbound_only?: boolean;
2615
+ /** @enum {string} */
2616
+ billing_tier?: "rental_iphone" | "rental_android" | "byod_imessage" | "byod_android" | "shared_pool" | "shared_imessage";
2540
2617
  };
2541
2618
  };
2542
2619
  };
2543
2620
  responses: {
2544
- /** @description Checkout session */
2545
- 200: {
2621
+ /** @description Pre-order draft created */
2622
+ 201: {
2546
2623
  headers: {
2547
2624
  [name: string]: unknown;
2548
2625
  };
2549
2626
  content: {
2550
- "application/json": components["schemas"]["Checkout"];
2627
+ "application/json": {
2628
+ /** @enum {boolean} */
2629
+ success: true;
2630
+ data: components["schemas"]["PreOrder"];
2631
+ trace_id: string;
2632
+ request_id: string;
2633
+ };
2551
2634
  };
2552
2635
  };
2553
2636
  /** @description Bad request — validation failed */
@@ -2568,15 +2651,6 @@ export interface paths {
2568
2651
  "application/json": components["schemas"]["ErrorEnvelope"];
2569
2652
  };
2570
2653
  };
2571
- /** @description Not found */
2572
- 404: {
2573
- headers: {
2574
- [name: string]: unknown;
2575
- };
2576
- content: {
2577
- "application/json": components["schemas"]["ErrorEnvelope"];
2578
- };
2579
- };
2580
2654
  /** @description Too many requests — rate limit exceeded */
2581
2655
  429: {
2582
2656
  headers: {
@@ -2603,7 +2677,7 @@ export interface paths {
2603
2677
  patch?: never;
2604
2678
  trace?: never;
2605
2679
  };
2606
- "/v1/billing/byod-checkout": {
2680
+ "/v1/pre-orders/{id}/confirm": {
2607
2681
  parameters: {
2608
2682
  query?: never;
2609
2683
  header?: never;
@@ -2613,29 +2687,33 @@ export interface paths {
2613
2687
  get?: never;
2614
2688
  put?: never;
2615
2689
  /**
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.
2690
+ * Confirm a pre-order draft
2691
+ * @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.
2618
2692
  */
2619
2693
  post: {
2620
2694
  parameters: {
2621
2695
  query?: never;
2622
2696
  header?: never;
2623
- path?: never;
2624
- cookie?: never;
2625
- };
2626
- requestBody?: {
2627
- content: {
2628
- "application/json": components["schemas"]["ByodCheckoutBody"];
2697
+ path: {
2698
+ id: string;
2629
2699
  };
2700
+ cookie?: never;
2630
2701
  };
2702
+ requestBody?: never;
2631
2703
  responses: {
2632
- /** @description BYOD checkout session */
2704
+ /** @description Pre-order confirmed (now provisioning) */
2633
2705
  200: {
2634
2706
  headers: {
2635
2707
  [name: string]: unknown;
2636
2708
  };
2637
2709
  content: {
2638
- "application/json": components["schemas"]["ByodCheckoutEnvelope"];
2710
+ "application/json": {
2711
+ /** @enum {boolean} */
2712
+ success: true;
2713
+ data: components["schemas"]["PreOrder"];
2714
+ trace_id: string;
2715
+ request_id: string;
2716
+ };
2639
2717
  };
2640
2718
  };
2641
2719
  /** @description Bad request — validation failed */
@@ -2656,6 +2734,15 @@ export interface paths {
2656
2734
  "application/json": components["schemas"]["ErrorEnvelope"];
2657
2735
  };
2658
2736
  };
2737
+ /** @description Not found */
2738
+ 404: {
2739
+ headers: {
2740
+ [name: string]: unknown;
2741
+ };
2742
+ content: {
2743
+ "application/json": components["schemas"]["ErrorEnvelope"];
2744
+ };
2745
+ };
2659
2746
  /** @description Too many requests — rate limit exceeded */
2660
2747
  429: {
2661
2748
  headers: {
@@ -2682,7 +2769,7 @@ export interface paths {
2682
2769
  patch?: never;
2683
2770
  trace?: never;
2684
2771
  };
2685
- "/v1/billing/subscriptions": {
2772
+ "/v1/pre-orders/{id}": {
2686
2773
  parameters: {
2687
2774
  query?: never;
2688
2775
  header?: never;
@@ -2690,25 +2777,33 @@ export interface paths {
2690
2777
  cookie?: never;
2691
2778
  };
2692
2779
  /**
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.
2780
+ * Get a pre-order
2781
+ * @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.
2695
2782
  */
2696
2783
  get: {
2697
2784
  parameters: {
2698
2785
  query?: never;
2699
2786
  header?: never;
2700
- path?: never;
2787
+ path: {
2788
+ id: string;
2789
+ };
2701
2790
  cookie?: never;
2702
2791
  };
2703
2792
  requestBody?: never;
2704
2793
  responses: {
2705
- /** @description Subscription list */
2794
+ /** @description Pre-order */
2706
2795
  200: {
2707
2796
  headers: {
2708
2797
  [name: string]: unknown;
2709
2798
  };
2710
2799
  content: {
2711
- "application/json": components["schemas"]["SubscriptionListEnvelope"];
2800
+ "application/json": {
2801
+ /** @enum {boolean} */
2802
+ success: true;
2803
+ data: components["schemas"]["PreOrder"];
2804
+ trace_id: string;
2805
+ request_id: string;
2806
+ };
2712
2807
  };
2713
2808
  };
2714
2809
  /** @description Bad request — validation failed */
@@ -2729,8 +2824,8 @@ export interface paths {
2729
2824
  "application/json": components["schemas"]["ErrorEnvelope"];
2730
2825
  };
2731
2826
  };
2732
- /** @description Too many requests — rate limit exceeded */
2733
- 429: {
2827
+ /** @description Not found */
2828
+ 404: {
2734
2829
  headers: {
2735
2830
  [name: string]: unknown;
2736
2831
  };
@@ -2738,8 +2833,17 @@ export interface paths {
2738
2833
  "application/json": components["schemas"]["ErrorEnvelope"];
2739
2834
  };
2740
2835
  };
2741
- /** @description Internal server error */
2742
- 500: {
2836
+ /** @description Too many requests — rate limit exceeded */
2837
+ 429: {
2838
+ headers: {
2839
+ [name: string]: unknown;
2840
+ };
2841
+ content: {
2842
+ "application/json": components["schemas"]["ErrorEnvelope"];
2843
+ };
2844
+ };
2845
+ /** @description Internal server error */
2846
+ 500: {
2743
2847
  headers: {
2744
2848
  [name: string]: unknown;
2745
2849
  };
@@ -2757,33 +2861,43 @@ export interface paths {
2757
2861
  patch?: never;
2758
2862
  trace?: never;
2759
2863
  };
2760
- "/v1/billing/invoices": {
2864
+ "/v1/pre-orders/{id}/cancel": {
2761
2865
  parameters: {
2762
2866
  query?: never;
2763
2867
  header?: never;
2764
2868
  path?: never;
2765
2869
  cookie?: never;
2766
2870
  };
2871
+ get?: never;
2872
+ put?: never;
2767
2873
  /**
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.
2874
+ * Cancel a pre-order draft
2875
+ * @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.
2770
2876
  */
2771
- get: {
2877
+ post: {
2772
2878
  parameters: {
2773
2879
  query?: never;
2774
2880
  header?: never;
2775
- path?: never;
2881
+ path: {
2882
+ id: string;
2883
+ };
2776
2884
  cookie?: never;
2777
2885
  };
2778
2886
  requestBody?: never;
2779
2887
  responses: {
2780
- /** @description Invoice list */
2888
+ /** @description Pre-order cancelled */
2781
2889
  200: {
2782
2890
  headers: {
2783
2891
  [name: string]: unknown;
2784
2892
  };
2785
2893
  content: {
2786
- "application/json": components["schemas"]["InvoiceListEnvelope"];
2894
+ "application/json": {
2895
+ /** @enum {boolean} */
2896
+ success: true;
2897
+ data: components["schemas"]["PreOrder"];
2898
+ trace_id: string;
2899
+ request_id: string;
2900
+ };
2787
2901
  };
2788
2902
  };
2789
2903
  /** @description Bad request — validation failed */
@@ -2804,6 +2918,24 @@ export interface paths {
2804
2918
  "application/json": components["schemas"]["ErrorEnvelope"];
2805
2919
  };
2806
2920
  };
2921
+ /** @description Not found */
2922
+ 404: {
2923
+ headers: {
2924
+ [name: string]: unknown;
2925
+ };
2926
+ content: {
2927
+ "application/json": components["schemas"]["ErrorEnvelope"];
2928
+ };
2929
+ };
2930
+ /** @description Conflict — concurrent or terminal state */
2931
+ 409: {
2932
+ headers: {
2933
+ [name: string]: unknown;
2934
+ };
2935
+ content: {
2936
+ "application/json": components["schemas"]["ErrorEnvelope"];
2937
+ };
2938
+ };
2807
2939
  /** @description Too many requests — rate limit exceeded */
2808
2940
  429: {
2809
2941
  headers: {
@@ -2824,15 +2956,13 @@ export interface paths {
2824
2956
  };
2825
2957
  };
2826
2958
  };
2827
- put?: never;
2828
- post?: never;
2829
2959
  delete?: never;
2830
2960
  options?: never;
2831
2961
  head?: never;
2832
2962
  patch?: never;
2833
2963
  trace?: never;
2834
2964
  };
2835
- "/v1/billing/subscriptions/{id}": {
2965
+ "/v1/billing/checkout": {
2836
2966
  parameters: {
2837
2967
  query?: never;
2838
2968
  header?: never;
@@ -2841,29 +2971,41 @@ export interface paths {
2841
2971
  };
2842
2972
  get?: never;
2843
2973
  put?: never;
2844
- post?: never;
2845
2974
  /**
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.
2975
+ * Start a Stripe Checkout for a reserved line
2976
+ * @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.
2848
2977
  */
2849
- delete: {
2978
+ post: {
2850
2979
  parameters: {
2851
2980
  query?: never;
2852
2981
  header?: never;
2853
- path: {
2854
- id: string;
2855
- };
2982
+ path?: never;
2856
2983
  cookie?: never;
2857
2984
  };
2858
- requestBody?: never;
2985
+ requestBody?: {
2986
+ content: {
2987
+ "application/json": {
2988
+ /**
2989
+ * @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`.
2990
+ * @example line_2k9Qp7RtY4mNb8VcXs3Wd6Lf1
2991
+ */
2992
+ line_id: string;
2993
+ /**
2994
+ * @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`.
2995
+ * @default false
2996
+ */
2997
+ inbound_only?: boolean;
2998
+ };
2999
+ };
3000
+ };
2859
3001
  responses: {
2860
- /** @description Subscription cancelled */
3002
+ /** @description Checkout session */
2861
3003
  200: {
2862
3004
  headers: {
2863
3005
  [name: string]: unknown;
2864
3006
  };
2865
3007
  content: {
2866
- "application/json": components["schemas"]["BillingMessage"];
3008
+ "application/json": components["schemas"]["Checkout"];
2867
3009
  };
2868
3010
  };
2869
3011
  /** @description Bad request — validation failed */
@@ -2893,15 +3035,6 @@ export interface paths {
2893
3035
  "application/json": components["schemas"]["ErrorEnvelope"];
2894
3036
  };
2895
3037
  };
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
3038
  /** @description Too many requests — rate limit exceeded */
2906
3039
  429: {
2907
3040
  headers: {
@@ -2922,38 +3055,45 @@ export interface paths {
2922
3055
  };
2923
3056
  };
2924
3057
  };
3058
+ delete?: never;
2925
3059
  options?: never;
2926
3060
  head?: never;
2927
3061
  patch?: never;
2928
3062
  trace?: never;
2929
3063
  };
2930
- "/v1/billing/payment-methods": {
3064
+ "/v1/billing/byod-checkout": {
2931
3065
  parameters: {
2932
3066
  query?: never;
2933
3067
  header?: never;
2934
3068
  path?: never;
2935
3069
  cookie?: never;
2936
3070
  };
3071
+ get?: never;
3072
+ put?: never;
2937
3073
  /**
2938
- * List payment methods
2939
- * @description List the saved payment methods on the authenticated user's Stripe customer. Each entry exposes the brand (`visa`, `mastercard`, etc.), the last four digits, and the default-method flag never the PAN.
3074
+ * Start a Stripe Checkout for BYOD slots
3075
+ * @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.
2940
3076
  */
2941
- get: {
3077
+ post: {
2942
3078
  parameters: {
2943
3079
  query?: never;
2944
3080
  header?: never;
2945
3081
  path?: never;
2946
3082
  cookie?: never;
2947
3083
  };
2948
- requestBody?: never;
3084
+ requestBody?: {
3085
+ content: {
3086
+ "application/json": components["schemas"]["ByodCheckoutBody"];
3087
+ };
3088
+ };
2949
3089
  responses: {
2950
- /** @description Payment methods */
3090
+ /** @description BYOD checkout session */
2951
3091
  200: {
2952
3092
  headers: {
2953
3093
  [name: string]: unknown;
2954
3094
  };
2955
3095
  content: {
2956
- "application/json": components["schemas"]["PaymentMethodListEnvelope"];
3096
+ "application/json": components["schemas"]["ByodCheckoutEnvelope"];
2957
3097
  };
2958
3098
  };
2959
3099
  /** @description Bad request — validation failed */
@@ -2994,10 +3134,24 @@ export interface paths {
2994
3134
  };
2995
3135
  };
2996
3136
  };
3137
+ delete?: never;
3138
+ options?: never;
3139
+ head?: never;
3140
+ patch?: never;
3141
+ trace?: never;
3142
+ };
3143
+ "/v1/billing/mac-checkout": {
3144
+ parameters: {
3145
+ query?: never;
3146
+ header?: never;
3147
+ path?: never;
3148
+ cookie?: never;
3149
+ };
3150
+ get?: never;
2997
3151
  put?: never;
2998
3152
  /**
2999
- * Start adding a payment method
3000
- * @description Create a Stripe SetupIntent so the customer can save a new card. The returned `client_secret` feeds directly into Stripe Elements / PaymentSheet on the partner UI; the resulting payment method is attached to the customer record on completion.
3153
+ * Start a Stripe Checkout for hosted macOS sessions
3154
+ * @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.
3001
3155
  */
3002
3156
  post: {
3003
3157
  parameters: {
@@ -3006,15 +3160,19 @@ export interface paths {
3006
3160
  path?: never;
3007
3161
  cookie?: never;
3008
3162
  };
3009
- requestBody?: never;
3163
+ requestBody?: {
3164
+ content: {
3165
+ "application/json": components["schemas"]["MacCheckoutBody"];
3166
+ };
3167
+ };
3010
3168
  responses: {
3011
- /** @description Setup intent */
3169
+ /** @description macOS session checkout */
3012
3170
  200: {
3013
3171
  headers: {
3014
3172
  [name: string]: unknown;
3015
3173
  };
3016
3174
  content: {
3017
- "application/json": components["schemas"]["SetupIntent"];
3175
+ "application/json": components["schemas"]["MacCheckoutEnvelope"];
3018
3176
  };
3019
3177
  };
3020
3178
  /** @description Bad request — validation failed */
@@ -3035,6 +3193,24 @@ export interface paths {
3035
3193
  "application/json": components["schemas"]["ErrorEnvelope"];
3036
3194
  };
3037
3195
  };
3196
+ /** @description Not found */
3197
+ 404: {
3198
+ headers: {
3199
+ [name: string]: unknown;
3200
+ };
3201
+ content: {
3202
+ "application/json": components["schemas"]["ErrorEnvelope"];
3203
+ };
3204
+ };
3205
+ /** @description Unprocessable — domain rule violation */
3206
+ 422: {
3207
+ headers: {
3208
+ [name: string]: unknown;
3209
+ };
3210
+ content: {
3211
+ "application/json": components["schemas"]["ErrorEnvelope"];
3212
+ };
3213
+ };
3038
3214
  /** @description Too many requests — rate limit exceeded */
3039
3215
  429: {
3040
3216
  headers: {
@@ -3061,7 +3237,7 @@ export interface paths {
3061
3237
  patch?: never;
3062
3238
  trace?: never;
3063
3239
  };
3064
- "/v1/billing/payment-methods/{id}": {
3240
+ "/v1/billing/subscriptions/{id}": {
3065
3241
  parameters: {
3066
3242
  query?: never;
3067
3243
  header?: never;
@@ -3072,8 +3248,8 @@ export interface paths {
3072
3248
  put?: never;
3073
3249
  post?: never;
3074
3250
  /**
3075
- * Remove a payment method
3076
- * @description Detach the payment method from the Stripe customer. Active subscriptions paying with this method will retry against the new default; ensure another method is set as default first.
3251
+ * Cancel a subscription
3252
+ * @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.
3077
3253
  */
3078
3254
  delete: {
3079
3255
  parameters: {
@@ -3086,7 +3262,7 @@ export interface paths {
3086
3262
  };
3087
3263
  requestBody?: never;
3088
3264
  responses: {
3089
- /** @description Payment method removed */
3265
+ /** @description Subscription cancelled */
3090
3266
  200: {
3091
3267
  headers: {
3092
3268
  [name: string]: unknown;
@@ -3122,6 +3298,15 @@ export interface paths {
3122
3298
  "application/json": components["schemas"]["ErrorEnvelope"];
3123
3299
  };
3124
3300
  };
3301
+ /** @description Conflict — concurrent or terminal state */
3302
+ 409: {
3303
+ headers: {
3304
+ [name: string]: unknown;
3305
+ };
3306
+ content: {
3307
+ "application/json": components["schemas"]["ErrorEnvelope"];
3308
+ };
3309
+ };
3125
3310
  /** @description Too many requests — rate limit exceeded */
3126
3311
  429: {
3127
3312
  headers: {
@@ -3144,23 +3329,11 @@ export interface paths {
3144
3329
  };
3145
3330
  options?: never;
3146
3331
  head?: never;
3147
- patch?: never;
3148
- trace?: never;
3149
- };
3150
- "/v1/billing/payment-methods/{id}/default": {
3151
- parameters: {
3152
- query?: never;
3153
- header?: never;
3154
- path?: never;
3155
- cookie?: never;
3156
- };
3157
- get?: never;
3158
- put?: never;
3159
3332
  /**
3160
- * Set default payment method
3161
- * @description Mark the payment method as the default for invoice charges. Future invoices on existing subscriptions automatically draw from this card.
3333
+ * Change a subscription’s quantity
3334
+ * @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`.
3162
3335
  */
3163
- post: {
3336
+ patch: {
3164
3337
  parameters: {
3165
3338
  query?: never;
3166
3339
  header?: never;
@@ -3169,15 +3342,19 @@ export interface paths {
3169
3342
  };
3170
3343
  cookie?: never;
3171
3344
  };
3172
- requestBody?: never;
3345
+ requestBody?: {
3346
+ content: {
3347
+ "application/json": components["schemas"]["SubscriptionQuantityBody"];
3348
+ };
3349
+ };
3173
3350
  responses: {
3174
- /** @description Default payment method updated */
3351
+ /** @description Quantity updated */
3175
3352
  200: {
3176
3353
  headers: {
3177
3354
  [name: string]: unknown;
3178
3355
  };
3179
3356
  content: {
3180
- "application/json": components["schemas"]["BillingMessage"];
3357
+ "application/json": components["schemas"]["SubscriptionQuantityEnvelope"];
3181
3358
  };
3182
3359
  };
3183
3360
  /** @description Bad request — validation failed */
@@ -3198,6 +3375,15 @@ export interface paths {
3198
3375
  "application/json": components["schemas"]["ErrorEnvelope"];
3199
3376
  };
3200
3377
  };
3378
+ /** @description Forbidden — caller authenticated but not allowed */
3379
+ 403: {
3380
+ headers: {
3381
+ [name: string]: unknown;
3382
+ };
3383
+ content: {
3384
+ "application/json": components["schemas"]["ErrorEnvelope"];
3385
+ };
3386
+ };
3201
3387
  /** @description Not found */
3202
3388
  404: {
3203
3389
  headers: {
@@ -3207,6 +3393,24 @@ export interface paths {
3207
3393
  "application/json": components["schemas"]["ErrorEnvelope"];
3208
3394
  };
3209
3395
  };
3396
+ /** @description Conflict — concurrent or terminal state */
3397
+ 409: {
3398
+ headers: {
3399
+ [name: string]: unknown;
3400
+ };
3401
+ content: {
3402
+ "application/json": components["schemas"]["ErrorEnvelope"];
3403
+ };
3404
+ };
3405
+ /** @description Unprocessable — domain rule violation */
3406
+ 422: {
3407
+ headers: {
3408
+ [name: string]: unknown;
3409
+ };
3410
+ content: {
3411
+ "application/json": components["schemas"]["ErrorEnvelope"];
3412
+ };
3413
+ };
3210
3414
  /** @description Too many requests — rate limit exceeded */
3211
3415
  429: {
3212
3416
  headers: {
@@ -3227,13 +3431,9 @@ export interface paths {
3227
3431
  };
3228
3432
  };
3229
3433
  };
3230
- delete?: never;
3231
- options?: never;
3232
- head?: never;
3233
- patch?: never;
3234
3434
  trace?: never;
3235
3435
  };
3236
- "/v1/messages": {
3436
+ "/v1/billing/subscriptions": {
3237
3437
  parameters: {
3238
3438
  query?: never;
3239
3439
  header?: never;
@@ -3241,32 +3441,25 @@ export interface paths {
3241
3441
  cookie?: never;
3242
3442
  };
3243
3443
  /**
3244
- * 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.
3444
+ * List Stripe subscriptions
3445
+ * @description Return every active Stripe subscription on the authenticated user's customer recordboth per-line rental subs and BYOD slot subs. Period boundaries, line link, price ID, and cancellation flag are surfaced verbatim from Stripe.
3246
3446
  */
3247
3447
  get: {
3248
3448
  parameters: {
3249
- query?: {
3250
- line_id?: string;
3251
- status?: "queued" | "sent" | "delivered" | "read" | "failed";
3252
- direction?: "outbound" | "inbound";
3253
- kind?: "message" | "voice" | "reaction";
3254
- cursor?: string;
3255
- limit?: number;
3256
- };
3449
+ query?: never;
3257
3450
  header?: never;
3258
3451
  path?: never;
3259
3452
  cookie?: never;
3260
3453
  };
3261
3454
  requestBody?: never;
3262
3455
  responses: {
3263
- /** @description Cursored message list */
3456
+ /** @description Subscription list */
3264
3457
  200: {
3265
3458
  headers: {
3266
3459
  [name: string]: unknown;
3267
3460
  };
3268
3461
  content: {
3269
- "application/json": components["schemas"]["CursoredMessagesEnvelope"];
3462
+ "application/json": components["schemas"]["SubscriptionListEnvelope"];
3270
3463
  };
3271
3464
  };
3272
3465
  /** @description Bad request — validation failed */
@@ -3308,15 +3501,478 @@ export interface paths {
3308
3501
  };
3309
3502
  };
3310
3503
  put?: never;
3311
- /**
3312
- * Send a message
3313
- * @description Queue an outbound iMessage / SMS / RCS message on the specified line. The device picks the actual transport at delivery time (`channel: "auto"` is the only supported value in v1). Returns `202` with the queued message envelope; the message progresses through `queued → sent → delivered → read` (or `failed`) reflected via webhooks. Quota-deferred sends return `status: "scheduled"` with a future `scheduled_for`.
3314
- */
3315
- post: {
3316
- parameters: {
3317
- query?: never;
3318
- header?: never;
3319
- path?: never;
3504
+ post?: never;
3505
+ delete?: never;
3506
+ options?: never;
3507
+ head?: never;
3508
+ patch?: never;
3509
+ trace?: never;
3510
+ };
3511
+ "/v1/billing/invoices": {
3512
+ parameters: {
3513
+ query?: never;
3514
+ header?: never;
3515
+ path?: never;
3516
+ cookie?: never;
3517
+ };
3518
+ /**
3519
+ * List Stripe invoices
3520
+ * @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.
3521
+ */
3522
+ get: {
3523
+ parameters: {
3524
+ query?: never;
3525
+ header?: never;
3526
+ path?: never;
3527
+ cookie?: never;
3528
+ };
3529
+ requestBody?: never;
3530
+ responses: {
3531
+ /** @description Invoice list */
3532
+ 200: {
3533
+ headers: {
3534
+ [name: string]: unknown;
3535
+ };
3536
+ content: {
3537
+ "application/json": components["schemas"]["InvoiceListEnvelope"];
3538
+ };
3539
+ };
3540
+ /** @description Bad request — validation failed */
3541
+ 400: {
3542
+ headers: {
3543
+ [name: string]: unknown;
3544
+ };
3545
+ content: {
3546
+ "application/json": components["schemas"]["ErrorEnvelope"];
3547
+ };
3548
+ };
3549
+ /** @description Unauthenticated — missing or invalid bearer token */
3550
+ 401: {
3551
+ headers: {
3552
+ [name: string]: unknown;
3553
+ };
3554
+ content: {
3555
+ "application/json": components["schemas"]["ErrorEnvelope"];
3556
+ };
3557
+ };
3558
+ /** @description Too many requests — rate limit exceeded */
3559
+ 429: {
3560
+ headers: {
3561
+ [name: string]: unknown;
3562
+ };
3563
+ content: {
3564
+ "application/json": components["schemas"]["ErrorEnvelope"];
3565
+ };
3566
+ };
3567
+ /** @description Internal server error */
3568
+ 500: {
3569
+ headers: {
3570
+ [name: string]: unknown;
3571
+ };
3572
+ content: {
3573
+ "application/json": components["schemas"]["ErrorEnvelope"];
3574
+ };
3575
+ };
3576
+ };
3577
+ };
3578
+ put?: never;
3579
+ post?: never;
3580
+ delete?: never;
3581
+ options?: never;
3582
+ head?: never;
3583
+ patch?: never;
3584
+ trace?: never;
3585
+ };
3586
+ "/v1/billing/payment-methods": {
3587
+ parameters: {
3588
+ query?: never;
3589
+ header?: never;
3590
+ path?: never;
3591
+ cookie?: never;
3592
+ };
3593
+ /**
3594
+ * List payment methods
3595
+ * @description List the saved payment methods on the authenticated user's Stripe customer. Each entry exposes the brand (`visa`, `mastercard`, etc.), the last four digits, and the default-method flag — never the PAN.
3596
+ */
3597
+ get: {
3598
+ parameters: {
3599
+ query?: never;
3600
+ header?: never;
3601
+ path?: never;
3602
+ cookie?: never;
3603
+ };
3604
+ requestBody?: never;
3605
+ responses: {
3606
+ /** @description Payment methods */
3607
+ 200: {
3608
+ headers: {
3609
+ [name: string]: unknown;
3610
+ };
3611
+ content: {
3612
+ "application/json": components["schemas"]["PaymentMethodListEnvelope"];
3613
+ };
3614
+ };
3615
+ /** @description Bad request — validation failed */
3616
+ 400: {
3617
+ headers: {
3618
+ [name: string]: unknown;
3619
+ };
3620
+ content: {
3621
+ "application/json": components["schemas"]["ErrorEnvelope"];
3622
+ };
3623
+ };
3624
+ /** @description Unauthenticated — missing or invalid bearer token */
3625
+ 401: {
3626
+ headers: {
3627
+ [name: string]: unknown;
3628
+ };
3629
+ content: {
3630
+ "application/json": components["schemas"]["ErrorEnvelope"];
3631
+ };
3632
+ };
3633
+ /** @description Too many requests — rate limit exceeded */
3634
+ 429: {
3635
+ headers: {
3636
+ [name: string]: unknown;
3637
+ };
3638
+ content: {
3639
+ "application/json": components["schemas"]["ErrorEnvelope"];
3640
+ };
3641
+ };
3642
+ /** @description Internal server error */
3643
+ 500: {
3644
+ headers: {
3645
+ [name: string]: unknown;
3646
+ };
3647
+ content: {
3648
+ "application/json": components["schemas"]["ErrorEnvelope"];
3649
+ };
3650
+ };
3651
+ };
3652
+ };
3653
+ put?: never;
3654
+ /**
3655
+ * Start adding a payment method
3656
+ * @description Create a Stripe SetupIntent so the customer can save a new card. The returned `client_secret` feeds directly into Stripe Elements / PaymentSheet on the partner UI; the resulting payment method is attached to the customer record on completion.
3657
+ */
3658
+ post: {
3659
+ parameters: {
3660
+ query?: never;
3661
+ header?: never;
3662
+ path?: never;
3663
+ cookie?: never;
3664
+ };
3665
+ requestBody?: never;
3666
+ responses: {
3667
+ /** @description Setup intent */
3668
+ 200: {
3669
+ headers: {
3670
+ [name: string]: unknown;
3671
+ };
3672
+ content: {
3673
+ "application/json": components["schemas"]["SetupIntent"];
3674
+ };
3675
+ };
3676
+ /** @description Bad request — validation failed */
3677
+ 400: {
3678
+ headers: {
3679
+ [name: string]: unknown;
3680
+ };
3681
+ content: {
3682
+ "application/json": components["schemas"]["ErrorEnvelope"];
3683
+ };
3684
+ };
3685
+ /** @description Unauthenticated — missing or invalid bearer token */
3686
+ 401: {
3687
+ headers: {
3688
+ [name: string]: unknown;
3689
+ };
3690
+ content: {
3691
+ "application/json": components["schemas"]["ErrorEnvelope"];
3692
+ };
3693
+ };
3694
+ /** @description Too many requests — rate limit exceeded */
3695
+ 429: {
3696
+ headers: {
3697
+ [name: string]: unknown;
3698
+ };
3699
+ content: {
3700
+ "application/json": components["schemas"]["ErrorEnvelope"];
3701
+ };
3702
+ };
3703
+ /** @description Internal server error */
3704
+ 500: {
3705
+ headers: {
3706
+ [name: string]: unknown;
3707
+ };
3708
+ content: {
3709
+ "application/json": components["schemas"]["ErrorEnvelope"];
3710
+ };
3711
+ };
3712
+ };
3713
+ };
3714
+ delete?: never;
3715
+ options?: never;
3716
+ head?: never;
3717
+ patch?: never;
3718
+ trace?: never;
3719
+ };
3720
+ "/v1/billing/payment-methods/{id}": {
3721
+ parameters: {
3722
+ query?: never;
3723
+ header?: never;
3724
+ path?: never;
3725
+ cookie?: never;
3726
+ };
3727
+ get?: never;
3728
+ put?: never;
3729
+ post?: never;
3730
+ /**
3731
+ * Remove a payment method
3732
+ * @description Detach the payment method from the Stripe customer. Active subscriptions paying with this method will retry against the new default; ensure another method is set as default first.
3733
+ */
3734
+ delete: {
3735
+ parameters: {
3736
+ query?: never;
3737
+ header?: never;
3738
+ path: {
3739
+ id: string;
3740
+ };
3741
+ cookie?: never;
3742
+ };
3743
+ requestBody?: never;
3744
+ responses: {
3745
+ /** @description Payment method removed */
3746
+ 200: {
3747
+ headers: {
3748
+ [name: string]: unknown;
3749
+ };
3750
+ content: {
3751
+ "application/json": components["schemas"]["BillingMessage"];
3752
+ };
3753
+ };
3754
+ /** @description Bad request — validation failed */
3755
+ 400: {
3756
+ headers: {
3757
+ [name: string]: unknown;
3758
+ };
3759
+ content: {
3760
+ "application/json": components["schemas"]["ErrorEnvelope"];
3761
+ };
3762
+ };
3763
+ /** @description Unauthenticated — missing or invalid bearer token */
3764
+ 401: {
3765
+ headers: {
3766
+ [name: string]: unknown;
3767
+ };
3768
+ content: {
3769
+ "application/json": components["schemas"]["ErrorEnvelope"];
3770
+ };
3771
+ };
3772
+ /** @description Not found */
3773
+ 404: {
3774
+ headers: {
3775
+ [name: string]: unknown;
3776
+ };
3777
+ content: {
3778
+ "application/json": components["schemas"]["ErrorEnvelope"];
3779
+ };
3780
+ };
3781
+ /** @description Too many requests — rate limit exceeded */
3782
+ 429: {
3783
+ headers: {
3784
+ [name: string]: unknown;
3785
+ };
3786
+ content: {
3787
+ "application/json": components["schemas"]["ErrorEnvelope"];
3788
+ };
3789
+ };
3790
+ /** @description Internal server error */
3791
+ 500: {
3792
+ headers: {
3793
+ [name: string]: unknown;
3794
+ };
3795
+ content: {
3796
+ "application/json": components["schemas"]["ErrorEnvelope"];
3797
+ };
3798
+ };
3799
+ };
3800
+ };
3801
+ options?: never;
3802
+ head?: never;
3803
+ patch?: never;
3804
+ trace?: never;
3805
+ };
3806
+ "/v1/billing/payment-methods/{id}/default": {
3807
+ parameters: {
3808
+ query?: never;
3809
+ header?: never;
3810
+ path?: never;
3811
+ cookie?: never;
3812
+ };
3813
+ get?: never;
3814
+ put?: never;
3815
+ /**
3816
+ * Set default payment method
3817
+ * @description Mark the payment method as the default for invoice charges. Future invoices on existing subscriptions automatically draw from this card.
3818
+ */
3819
+ post: {
3820
+ parameters: {
3821
+ query?: never;
3822
+ header?: never;
3823
+ path: {
3824
+ id: string;
3825
+ };
3826
+ cookie?: never;
3827
+ };
3828
+ requestBody?: never;
3829
+ responses: {
3830
+ /** @description Default payment method updated */
3831
+ 200: {
3832
+ headers: {
3833
+ [name: string]: unknown;
3834
+ };
3835
+ content: {
3836
+ "application/json": components["schemas"]["BillingMessage"];
3837
+ };
3838
+ };
3839
+ /** @description Bad request — validation failed */
3840
+ 400: {
3841
+ headers: {
3842
+ [name: string]: unknown;
3843
+ };
3844
+ content: {
3845
+ "application/json": components["schemas"]["ErrorEnvelope"];
3846
+ };
3847
+ };
3848
+ /** @description Unauthenticated — missing or invalid bearer token */
3849
+ 401: {
3850
+ headers: {
3851
+ [name: string]: unknown;
3852
+ };
3853
+ content: {
3854
+ "application/json": components["schemas"]["ErrorEnvelope"];
3855
+ };
3856
+ };
3857
+ /** @description Not found */
3858
+ 404: {
3859
+ headers: {
3860
+ [name: string]: unknown;
3861
+ };
3862
+ content: {
3863
+ "application/json": components["schemas"]["ErrorEnvelope"];
3864
+ };
3865
+ };
3866
+ /** @description Too many requests — rate limit exceeded */
3867
+ 429: {
3868
+ headers: {
3869
+ [name: string]: unknown;
3870
+ };
3871
+ content: {
3872
+ "application/json": components["schemas"]["ErrorEnvelope"];
3873
+ };
3874
+ };
3875
+ /** @description Internal server error */
3876
+ 500: {
3877
+ headers: {
3878
+ [name: string]: unknown;
3879
+ };
3880
+ content: {
3881
+ "application/json": components["schemas"]["ErrorEnvelope"];
3882
+ };
3883
+ };
3884
+ };
3885
+ };
3886
+ delete?: never;
3887
+ options?: never;
3888
+ head?: never;
3889
+ patch?: never;
3890
+ trace?: never;
3891
+ };
3892
+ "/v1/messages": {
3893
+ parameters: {
3894
+ query?: never;
3895
+ header?: never;
3896
+ path?: never;
3897
+ cookie?: never;
3898
+ };
3899
+ /**
3900
+ * List messages
3901
+ * @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.
3902
+ */
3903
+ get: {
3904
+ parameters: {
3905
+ query?: {
3906
+ line_id?: string;
3907
+ status?: "queued" | "sent" | "delivered" | "read" | "failed";
3908
+ direction?: "outbound" | "inbound";
3909
+ kind?: "message" | "voice" | "reaction";
3910
+ cursor?: string;
3911
+ limit?: number;
3912
+ };
3913
+ header?: never;
3914
+ path?: never;
3915
+ cookie?: never;
3916
+ };
3917
+ requestBody?: never;
3918
+ responses: {
3919
+ /** @description Cursored message list */
3920
+ 200: {
3921
+ headers: {
3922
+ [name: string]: unknown;
3923
+ };
3924
+ content: {
3925
+ "application/json": components["schemas"]["CursoredMessagesEnvelope"];
3926
+ };
3927
+ };
3928
+ /** @description Bad request — validation failed */
3929
+ 400: {
3930
+ headers: {
3931
+ [name: string]: unknown;
3932
+ };
3933
+ content: {
3934
+ "application/json": components["schemas"]["ErrorEnvelope"];
3935
+ };
3936
+ };
3937
+ /** @description Unauthenticated — missing or invalid bearer token */
3938
+ 401: {
3939
+ headers: {
3940
+ [name: string]: unknown;
3941
+ };
3942
+ content: {
3943
+ "application/json": components["schemas"]["ErrorEnvelope"];
3944
+ };
3945
+ };
3946
+ /** @description Too many requests — rate limit exceeded */
3947
+ 429: {
3948
+ headers: {
3949
+ [name: string]: unknown;
3950
+ };
3951
+ content: {
3952
+ "application/json": components["schemas"]["ErrorEnvelope"];
3953
+ };
3954
+ };
3955
+ /** @description Internal server error */
3956
+ 500: {
3957
+ headers: {
3958
+ [name: string]: unknown;
3959
+ };
3960
+ content: {
3961
+ "application/json": components["schemas"]["ErrorEnvelope"];
3962
+ };
3963
+ };
3964
+ };
3965
+ };
3966
+ put?: never;
3967
+ /**
3968
+ * Send a message
3969
+ * @description Queue an outbound iMessage / SMS / RCS message on the specified line. The device picks the actual transport at delivery time (`channel: "auto"` is the only supported value in v1). Returns `202` with the queued message envelope; the message progresses through `queued → sent → delivered → read` (or `failed`) reflected via webhooks. Quota-deferred sends return `status: "scheduled"` with a future `scheduled_for`.
3970
+ */
3971
+ post: {
3972
+ parameters: {
3973
+ query?: never;
3974
+ header?: never;
3975
+ path?: never;
3320
3976
  cookie?: never;
3321
3977
  };
3322
3978
  requestBody?: {
@@ -3392,8 +4048,196 @@ export interface paths {
3392
4048
  "application/json": components["schemas"]["ErrorEnvelope"];
3393
4049
  };
3394
4050
  };
3395
- /** @description Not found */
3396
- 404: {
4051
+ /** @description Not found */
4052
+ 404: {
4053
+ headers: {
4054
+ [name: string]: unknown;
4055
+ };
4056
+ content: {
4057
+ "application/json": components["schemas"]["ErrorEnvelope"];
4058
+ };
4059
+ };
4060
+ /** @description Conflict — concurrent or terminal state */
4061
+ 409: {
4062
+ headers: {
4063
+ [name: string]: unknown;
4064
+ };
4065
+ content: {
4066
+ "application/json": components["schemas"]["ErrorEnvelope"];
4067
+ };
4068
+ };
4069
+ /** @description Unprocessable — domain rule violation */
4070
+ 422: {
4071
+ headers: {
4072
+ [name: string]: unknown;
4073
+ };
4074
+ content: {
4075
+ "application/json": components["schemas"]["ErrorEnvelope"];
4076
+ };
4077
+ };
4078
+ /** @description Too many requests — rate limit exceeded */
4079
+ 429: {
4080
+ headers: {
4081
+ [name: string]: unknown;
4082
+ };
4083
+ content: {
4084
+ "application/json": components["schemas"]["ErrorEnvelope"];
4085
+ };
4086
+ };
4087
+ /** @description Internal server error */
4088
+ 500: {
4089
+ headers: {
4090
+ [name: string]: unknown;
4091
+ };
4092
+ content: {
4093
+ "application/json": components["schemas"]["ErrorEnvelope"];
4094
+ };
4095
+ };
4096
+ };
4097
+ };
4098
+ delete?: never;
4099
+ options?: never;
4100
+ head?: never;
4101
+ patch?: never;
4102
+ trace?: never;
4103
+ };
4104
+ "/v1/messages/{id}/reactions": {
4105
+ parameters: {
4106
+ query?: never;
4107
+ header?: never;
4108
+ path?: never;
4109
+ cookie?: never;
4110
+ };
4111
+ get?: never;
4112
+ put?: never;
4113
+ /**
4114
+ * Add or replace a tapback
4115
+ * @description Add an iMessage tapback (love / like / dislike / laugh / emphasize / question) on a prior message you sent. Replace-on-add semantics: posting a different `type` against the same parent updates the existing reaction in place — switching from `like` to `love` is a single POST, no DELETE-then-POST dance. Reactions require iMessage; SMS / RCS parents are rejected with 422.
4116
+ */
4117
+ post: {
4118
+ parameters: {
4119
+ query?: never;
4120
+ header?: never;
4121
+ path: {
4122
+ id: string;
4123
+ };
4124
+ cookie?: never;
4125
+ };
4126
+ requestBody?: {
4127
+ content: {
4128
+ "application/json": components["schemas"]["SendReactionRequest"];
4129
+ };
4130
+ };
4131
+ responses: {
4132
+ /** @description Reaction queued (added or replaced) */
4133
+ 202: {
4134
+ headers: {
4135
+ [name: string]: unknown;
4136
+ };
4137
+ content: {
4138
+ "application/json": components["schemas"]["ReactionEnvelope"];
4139
+ };
4140
+ };
4141
+ /** @description Bad request — validation failed */
4142
+ 400: {
4143
+ headers: {
4144
+ [name: string]: unknown;
4145
+ };
4146
+ content: {
4147
+ "application/json": components["schemas"]["ErrorEnvelope"];
4148
+ };
4149
+ };
4150
+ /** @description Unauthenticated — missing or invalid bearer token */
4151
+ 401: {
4152
+ headers: {
4153
+ [name: string]: unknown;
4154
+ };
4155
+ content: {
4156
+ "application/json": components["schemas"]["ErrorEnvelope"];
4157
+ };
4158
+ };
4159
+ /** @description Forbidden — caller authenticated but not allowed */
4160
+ 403: {
4161
+ headers: {
4162
+ [name: string]: unknown;
4163
+ };
4164
+ content: {
4165
+ "application/json": components["schemas"]["ErrorEnvelope"];
4166
+ };
4167
+ };
4168
+ /** @description Not found */
4169
+ 404: {
4170
+ headers: {
4171
+ [name: string]: unknown;
4172
+ };
4173
+ content: {
4174
+ "application/json": components["schemas"]["ErrorEnvelope"];
4175
+ };
4176
+ };
4177
+ /** @description Unprocessable — domain rule violation */
4178
+ 422: {
4179
+ headers: {
4180
+ [name: string]: unknown;
4181
+ };
4182
+ content: {
4183
+ "application/json": components["schemas"]["ErrorEnvelope"];
4184
+ };
4185
+ };
4186
+ /** @description Too many requests — rate limit exceeded */
4187
+ 429: {
4188
+ headers: {
4189
+ [name: string]: unknown;
4190
+ };
4191
+ content: {
4192
+ "application/json": components["schemas"]["ErrorEnvelope"];
4193
+ };
4194
+ };
4195
+ /** @description Internal server error */
4196
+ 500: {
4197
+ headers: {
4198
+ [name: string]: unknown;
4199
+ };
4200
+ content: {
4201
+ "application/json": components["schemas"]["ErrorEnvelope"];
4202
+ };
4203
+ };
4204
+ };
4205
+ };
4206
+ /**
4207
+ * Remove a tapback
4208
+ * @description Remove the caller's tapback on a prior message. Idempotent — `removed: false` is a 200, not a 404, so partners can fire-and-forget without first checking whether a reaction was set. The response reports the type that was removed when applicable.
4209
+ */
4210
+ delete: {
4211
+ parameters: {
4212
+ query?: never;
4213
+ header?: never;
4214
+ path: {
4215
+ id: string;
4216
+ };
4217
+ cookie?: never;
4218
+ };
4219
+ requestBody?: never;
4220
+ responses: {
4221
+ /** @description Reaction removed (idempotent — no-op when nothing to remove) */
4222
+ 200: {
4223
+ headers: {
4224
+ [name: string]: unknown;
4225
+ };
4226
+ content: {
4227
+ "application/json": components["schemas"]["ReactionRemovalEnvelope"];
4228
+ };
4229
+ };
4230
+ /** @description Bad request — validation failed */
4231
+ 400: {
4232
+ headers: {
4233
+ [name: string]: unknown;
4234
+ };
4235
+ content: {
4236
+ "application/json": components["schemas"]["ErrorEnvelope"];
4237
+ };
4238
+ };
4239
+ /** @description Unauthenticated — missing or invalid bearer token */
4240
+ 401: {
3397
4241
  headers: {
3398
4242
  [name: string]: unknown;
3399
4243
  };
@@ -3401,8 +4245,8 @@ export interface paths {
3401
4245
  "application/json": components["schemas"]["ErrorEnvelope"];
3402
4246
  };
3403
4247
  };
3404
- /** @description Conflictconcurrent or terminal state */
3405
- 409: {
4248
+ /** @description Forbiddencaller authenticated but not allowed */
4249
+ 403: {
3406
4250
  headers: {
3407
4251
  [name: string]: unknown;
3408
4252
  };
@@ -3410,8 +4254,8 @@ export interface paths {
3410
4254
  "application/json": components["schemas"]["ErrorEnvelope"];
3411
4255
  };
3412
4256
  };
3413
- /** @description Unprocessable domain rule violation */
3414
- 422: {
4257
+ /** @description Not found */
4258
+ 404: {
3415
4259
  headers: {
3416
4260
  [name: string]: unknown;
3417
4261
  };
@@ -3439,26 +4283,23 @@ export interface paths {
3439
4283
  };
3440
4284
  };
3441
4285
  };
3442
- delete?: never;
3443
4286
  options?: never;
3444
4287
  head?: never;
3445
4288
  patch?: never;
3446
4289
  trace?: never;
3447
4290
  };
3448
- "/v1/messages/{id}/reactions": {
4291
+ "/v1/messages/{id}": {
3449
4292
  parameters: {
3450
4293
  query?: never;
3451
4294
  header?: never;
3452
4295
  path?: never;
3453
4296
  cookie?: never;
3454
4297
  };
3455
- get?: never;
3456
- put?: never;
3457
4298
  /**
3458
- * Add or replace a tapback
3459
- * @description Add an iMessage tapback (love / like / dislike / laugh / emphasize / question) on a prior message you sent. Replace-on-add semantics: posting a different `type` against the same parent updates the existing reaction in place switching from `like` to `love` is a single POST, no DELETE-then-POST dance. Reactions require iMessage; SMS / RCS parents are rejected with 422.
4299
+ * Get a message
4300
+ * @description Retrieve a single message by its `msg_<base62>` publicId. Returns the full message envelope with the current lifecycle status (`queued | scheduled | sent | delivered | read | failed | cancelled | received`). The `queue_info` field is always `null` on GET reads that snapshot is populated only on the POST response at send time.
3460
4301
  */
3461
- post: {
4302
+ get: {
3462
4303
  parameters: {
3463
4304
  query?: never;
3464
4305
  header?: never;
@@ -3467,19 +4308,15 @@ export interface paths {
3467
4308
  };
3468
4309
  cookie?: never;
3469
4310
  };
3470
- requestBody?: {
3471
- content: {
3472
- "application/json": components["schemas"]["SendReactionRequest"];
3473
- };
3474
- };
4311
+ requestBody?: never;
3475
4312
  responses: {
3476
- /** @description Reaction queued (added or replaced) */
3477
- 202: {
4313
+ /** @description Message */
4314
+ 200: {
3478
4315
  headers: {
3479
4316
  [name: string]: unknown;
3480
4317
  };
3481
4318
  content: {
3482
- "application/json": components["schemas"]["ReactionEnvelope"];
4319
+ "application/json": components["schemas"]["MessageV1Envelope"];
3483
4320
  };
3484
4321
  };
3485
4322
  /** @description Bad request — validation failed */
@@ -3518,15 +4355,6 @@ export interface paths {
3518
4355
  "application/json": components["schemas"]["ErrorEnvelope"];
3519
4356
  };
3520
4357
  };
3521
- /** @description Unprocessable — domain rule violation */
3522
- 422: {
3523
- headers: {
3524
- [name: string]: unknown;
3525
- };
3526
- content: {
3527
- "application/json": components["schemas"]["ErrorEnvelope"];
3528
- };
3529
- };
3530
4358
  /** @description Too many requests — rate limit exceeded */
3531
4359
  429: {
3532
4360
  headers: {
@@ -3547,9 +4375,11 @@ export interface paths {
3547
4375
  };
3548
4376
  };
3549
4377
  };
4378
+ put?: never;
4379
+ post?: never;
3550
4380
  /**
3551
- * Remove a tapback
3552
- * @description Remove the caller's tapback on a prior message. Idempotent `removed: false` is a 200, not a 404, so partners can fire-and-forget without first checking whether a reaction was set. The response reports the type that was removed when applicable.
4381
+ * Cancel a queued or scheduled message
4382
+ * @description Cancel a message before it leaves iSnap. Cancellable while the row is still `queued` or `scheduled` (and has not been pushed to a device); returns `409 too_late_to_cancel` once the device has picked it up. Decrements the daily quota when the row had been counted at send time.
3553
4383
  */
3554
4384
  delete: {
3555
4385
  parameters: {
@@ -3562,13 +4392,13 @@ export interface paths {
3562
4392
  };
3563
4393
  requestBody?: never;
3564
4394
  responses: {
3565
- /** @description Reaction removed (idempotent — no-op when nothing to remove) */
4395
+ /** @description Message cancelled */
3566
4396
  200: {
3567
4397
  headers: {
3568
4398
  [name: string]: unknown;
3569
4399
  };
3570
4400
  content: {
3571
- "application/json": components["schemas"]["ReactionRemovalEnvelope"];
4401
+ "application/json": components["schemas"]["MessageV1Envelope"];
3572
4402
  };
3573
4403
  };
3574
4404
  /** @description Bad request — validation failed */
@@ -3607,6 +4437,15 @@ export interface paths {
3607
4437
  "application/json": components["schemas"]["ErrorEnvelope"];
3608
4438
  };
3609
4439
  };
4440
+ /** @description Conflict — concurrent or terminal state */
4441
+ 409: {
4442
+ headers: {
4443
+ [name: string]: unknown;
4444
+ };
4445
+ content: {
4446
+ "application/json": components["schemas"]["ErrorEnvelope"];
4447
+ };
4448
+ };
3610
4449
  /** @description Too many requests — rate limit exceeded */
3611
4450
  429: {
3612
4451
  headers: {
@@ -3632,7 +4471,7 @@ export interface paths {
3632
4471
  patch?: never;
3633
4472
  trace?: never;
3634
4473
  };
3635
- "/v1/messages/{id}": {
4474
+ "/v1/webhooks": {
3636
4475
  parameters: {
3637
4476
  query?: never;
3638
4477
  header?: never;
@@ -3640,27 +4479,31 @@ export interface paths {
3640
4479
  cookie?: never;
3641
4480
  };
3642
4481
  /**
3643
- * Get a message
3644
- * @description Retrieve a single message by its `msg_<base62>` publicId. Returns the full message envelope with the current lifecycle status (`queued | scheduled | sent | delivered | read | failed | cancelled | received`). The `queue_info` field is always `null` on GET reads — that snapshot is populated only on the POST response at send time.
4482
+ * List webhook subscriptions
4483
+ * @description Return every webhook subscription owned by the authenticated user. Each entry surfaces the URL, the subscribed event list, the line allowlist, the active flag, the failure counter, and the last success / failure timestamps. Accepts an API key (`isnap_*`) or a dashboard JWT.
3645
4484
  */
3646
4485
  get: {
3647
4486
  parameters: {
3648
4487
  query?: never;
3649
4488
  header?: never;
3650
- path: {
3651
- id: string;
3652
- };
4489
+ path?: never;
3653
4490
  cookie?: never;
3654
4491
  };
3655
4492
  requestBody?: never;
3656
4493
  responses: {
3657
- /** @description Message */
4494
+ /** @description Webhook list */
3658
4495
  200: {
3659
4496
  headers: {
3660
4497
  [name: string]: unknown;
3661
4498
  };
3662
4499
  content: {
3663
- "application/json": components["schemas"]["MessageV1Envelope"];
4500
+ "application/json": {
4501
+ /** @enum {boolean} */
4502
+ success: true;
4503
+ data: components["schemas"]["WebhookList"];
4504
+ trace_id: string;
4505
+ request_id: string;
4506
+ };
3664
4507
  };
3665
4508
  };
3666
4509
  /** @description Bad request — validation failed */
@@ -3681,8 +4524,8 @@ export interface paths {
3681
4524
  "application/json": components["schemas"]["ErrorEnvelope"];
3682
4525
  };
3683
4526
  };
3684
- /** @description Forbidden caller authenticated but not allowed */
3685
- 403: {
4527
+ /** @description Too many requests rate limit exceeded */
4528
+ 429: {
3686
4529
  headers: {
3687
4530
  [name: string]: unknown;
3688
4531
  };
@@ -3690,8 +4533,66 @@ export interface paths {
3690
4533
  "application/json": components["schemas"]["ErrorEnvelope"];
3691
4534
  };
3692
4535
  };
3693
- /** @description Not found */
3694
- 404: {
4536
+ /** @description Internal server error */
4537
+ 500: {
4538
+ headers: {
4539
+ [name: string]: unknown;
4540
+ };
4541
+ content: {
4542
+ "application/json": components["schemas"]["ErrorEnvelope"];
4543
+ };
4544
+ };
4545
+ };
4546
+ };
4547
+ put?: never;
4548
+ /**
4549
+ * Register a webhook subscription
4550
+ * @description Subscribe a URL to a list of event types and (optionally) a line allowlist. The server signs deliveries with an HMAC-SHA256 secret returned once here. Retries follow exponential backoff; 10 consecutive failures auto-disable the subscription. Accepts an API key (`isnap_*`) or a dashboard JWT.
4551
+ */
4552
+ post: {
4553
+ parameters: {
4554
+ query?: never;
4555
+ header?: never;
4556
+ path?: never;
4557
+ cookie?: never;
4558
+ };
4559
+ requestBody?: {
4560
+ content: {
4561
+ "application/json": {
4562
+ /** Format: uri */
4563
+ url: string;
4564
+ events: components["schemas"]["WebhookEventName"][];
4565
+ line_ids?: string[];
4566
+ };
4567
+ };
4568
+ };
4569
+ responses: {
4570
+ /** @description Webhook created */
4571
+ 201: {
4572
+ headers: {
4573
+ [name: string]: unknown;
4574
+ };
4575
+ content: {
4576
+ "application/json": {
4577
+ /** @enum {boolean} */
4578
+ success: true;
4579
+ data: components["schemas"]["Webhook"];
4580
+ trace_id: string;
4581
+ request_id: string;
4582
+ };
4583
+ };
4584
+ };
4585
+ /** @description Bad request — validation failed */
4586
+ 400: {
4587
+ headers: {
4588
+ [name: string]: unknown;
4589
+ };
4590
+ content: {
4591
+ "application/json": components["schemas"]["ErrorEnvelope"];
4592
+ };
4593
+ };
4594
+ /** @description Unauthenticated — missing or invalid bearer token */
4595
+ 401: {
3695
4596
  headers: {
3696
4597
  [name: string]: unknown;
3697
4598
  };
@@ -3719,13 +4620,24 @@ export interface paths {
3719
4620
  };
3720
4621
  };
3721
4622
  };
3722
- put?: never;
3723
- post?: never;
4623
+ delete?: never;
4624
+ options?: never;
4625
+ head?: never;
4626
+ patch?: never;
4627
+ trace?: never;
4628
+ };
4629
+ "/v1/webhooks/{id}": {
4630
+ parameters: {
4631
+ query?: never;
4632
+ header?: never;
4633
+ path?: never;
4634
+ cookie?: never;
4635
+ };
3724
4636
  /**
3725
- * Cancel a queued or scheduled message
3726
- * @description Cancel a message before it leaves iSnap. Cancellable while the row is still `queued` or `scheduled` (and has not been pushed to a device); returns `409 too_late_to_cancel` once the device has picked it up. Decrements the daily quota when the row had been counted at send time.
4637
+ * Get a webhook subscription
4638
+ * @description Fetch a single webhook subscription by its `wh_<base62>` id. Returns the URL, the event list, the line allowlist, the active flag, and the metadata never the secret (rotate via the dedicated endpoint to mint a new one).
3727
4639
  */
3728
- delete: {
4640
+ get: {
3729
4641
  parameters: {
3730
4642
  query?: never;
3731
4643
  header?: never;
@@ -3736,13 +4648,13 @@ export interface paths {
3736
4648
  };
3737
4649
  requestBody?: never;
3738
4650
  responses: {
3739
- /** @description Message cancelled */
4651
+ /** @description Webhook */
3740
4652
  200: {
3741
4653
  headers: {
3742
4654
  [name: string]: unknown;
3743
4655
  };
3744
4656
  content: {
3745
- "application/json": components["schemas"]["MessageV1Envelope"];
4657
+ "application/json": components["schemas"]["WebhookV1Envelope"];
3746
4658
  };
3747
4659
  };
3748
4660
  /** @description Bad request — validation failed */
@@ -3763,8 +4675,8 @@ export interface paths {
3763
4675
  "application/json": components["schemas"]["ErrorEnvelope"];
3764
4676
  };
3765
4677
  };
3766
- /** @description Forbidden caller authenticated but not allowed */
3767
- 403: {
4678
+ /** @description Not found */
4679
+ 404: {
3768
4680
  headers: {
3769
4681
  [name: string]: unknown;
3770
4682
  };
@@ -3772,8 +4684,8 @@ export interface paths {
3772
4684
  "application/json": components["schemas"]["ErrorEnvelope"];
3773
4685
  };
3774
4686
  };
3775
- /** @description Not found */
3776
- 404: {
4687
+ /** @description Too many requests — rate limit exceeded */
4688
+ 429: {
3777
4689
  headers: {
3778
4690
  [name: string]: unknown;
3779
4691
  };
@@ -3781,8 +4693,8 @@ export interface paths {
3781
4693
  "application/json": components["schemas"]["ErrorEnvelope"];
3782
4694
  };
3783
4695
  };
3784
- /** @description Conflict concurrent or terminal state */
3785
- 409: {
4696
+ /** @description Internal server error */
4697
+ 500: {
3786
4698
  headers: {
3787
4699
  [name: string]: unknown;
3788
4700
  };
@@ -3790,8 +4702,34 @@ export interface paths {
3790
4702
  "application/json": components["schemas"]["ErrorEnvelope"];
3791
4703
  };
3792
4704
  };
3793
- /** @description Too many requests — rate limit exceeded */
3794
- 429: {
4705
+ };
4706
+ };
4707
+ put?: never;
4708
+ post?: never;
4709
+ /**
4710
+ * Delete a webhook subscription
4711
+ * @description Permanently delete the webhook subscription. In-flight deliveries already enqueued will still attempt — the deletion only stops new event matching. Accepts an API key (`isnap_*`) or a dashboard JWT.
4712
+ */
4713
+ delete: {
4714
+ parameters: {
4715
+ query?: never;
4716
+ header?: never;
4717
+ path: {
4718
+ id: string;
4719
+ };
4720
+ cookie?: never;
4721
+ };
4722
+ requestBody?: never;
4723
+ responses: {
4724
+ /** @description Webhook deleted */
4725
+ 204: {
4726
+ headers: {
4727
+ [name: string]: unknown;
4728
+ };
4729
+ content?: never;
4730
+ };
4731
+ /** @description Bad request — validation failed */
4732
+ 400: {
3795
4733
  headers: {
3796
4734
  [name: string]: unknown;
3797
4735
  };
@@ -3799,8 +4737,8 @@ export interface paths {
3799
4737
  "application/json": components["schemas"]["ErrorEnvelope"];
3800
4738
  };
3801
4739
  };
3802
- /** @description Internal server error */
3803
- 500: {
4740
+ /** @description Unauthenticated missing or invalid bearer token */
4741
+ 401: {
3804
4742
  headers: {
3805
4743
  [name: string]: unknown;
3806
4744
  };
@@ -3808,46 +4746,70 @@ export interface paths {
3808
4746
  "application/json": components["schemas"]["ErrorEnvelope"];
3809
4747
  };
3810
4748
  };
3811
- };
3812
- };
3813
- options?: never;
3814
- head?: never;
3815
- patch?: never;
3816
- trace?: never;
3817
- };
3818
- "/v1/webhooks": {
3819
- parameters: {
3820
- query?: never;
3821
- header?: never;
3822
- path?: never;
3823
- cookie?: never;
4749
+ /** @description Not found */
4750
+ 404: {
4751
+ headers: {
4752
+ [name: string]: unknown;
4753
+ };
4754
+ content: {
4755
+ "application/json": components["schemas"]["ErrorEnvelope"];
4756
+ };
4757
+ };
4758
+ /** @description Too many requests — rate limit exceeded */
4759
+ 429: {
4760
+ headers: {
4761
+ [name: string]: unknown;
4762
+ };
4763
+ content: {
4764
+ "application/json": components["schemas"]["ErrorEnvelope"];
4765
+ };
4766
+ };
4767
+ /** @description Internal server error */
4768
+ 500: {
4769
+ headers: {
4770
+ [name: string]: unknown;
4771
+ };
4772
+ content: {
4773
+ "application/json": components["schemas"]["ErrorEnvelope"];
4774
+ };
4775
+ };
4776
+ };
3824
4777
  };
4778
+ options?: never;
4779
+ head?: never;
3825
4780
  /**
3826
- * List webhook subscriptions
3827
- * @description Return every webhook subscription owned by the authenticated user. Each entry surfaces the URL, the subscribed event list, the line allowlist, the active flag, the failure counter, and the last success / failure timestamps. Accepts an API key (`isnap_*`) or a dashboard JWT.
4781
+ * Update a webhook subscription
4782
+ * @description Partial update of a webhook subscription. Any subset of `url`, `events`, `line_ids`, `description`, and `active` is accepted; at least one field is required. Event names are validated against the shared `WebhookEventName` enum so typos reject at 400.
3828
4783
  */
3829
- get: {
4784
+ patch: {
3830
4785
  parameters: {
3831
4786
  query?: never;
3832
4787
  header?: never;
3833
- path?: never;
4788
+ path: {
4789
+ id: string;
4790
+ };
3834
4791
  cookie?: never;
3835
4792
  };
3836
- requestBody?: never;
4793
+ requestBody?: {
4794
+ content: {
4795
+ "application/json": {
4796
+ /** Format: uri */
4797
+ url?: string;
4798
+ events?: components["schemas"]["WebhookEventName"][];
4799
+ line_ids?: string[] | null;
4800
+ description?: string | null;
4801
+ active?: boolean;
4802
+ };
4803
+ };
4804
+ };
3837
4805
  responses: {
3838
- /** @description Webhook list */
4806
+ /** @description Webhook updated */
3839
4807
  200: {
3840
4808
  headers: {
3841
4809
  [name: string]: unknown;
3842
4810
  };
3843
4811
  content: {
3844
- "application/json": {
3845
- /** @enum {boolean} */
3846
- success: true;
3847
- data: components["schemas"]["WebhookList"];
3848
- trace_id: string;
3849
- request_id: string;
3850
- };
4812
+ "application/json": components["schemas"]["WebhookV1Envelope"];
3851
4813
  };
3852
4814
  };
3853
4815
  /** @description Bad request — validation failed */
@@ -3868,6 +4830,15 @@ export interface paths {
3868
4830
  "application/json": components["schemas"]["ErrorEnvelope"];
3869
4831
  };
3870
4832
  };
4833
+ /** @description Not found */
4834
+ 404: {
4835
+ headers: {
4836
+ [name: string]: unknown;
4837
+ };
4838
+ content: {
4839
+ "application/json": components["schemas"]["ErrorEnvelope"];
4840
+ };
4841
+ };
3871
4842
  /** @description Too many requests — rate limit exceeded */
3872
4843
  429: {
3873
4844
  headers: {
@@ -3888,42 +4859,43 @@ export interface paths {
3888
4859
  };
3889
4860
  };
3890
4861
  };
3891
- put?: never;
4862
+ trace?: never;
4863
+ };
4864
+ "/v1/webhooks/events": {
4865
+ parameters: {
4866
+ query?: never;
4867
+ header?: never;
4868
+ path?: never;
4869
+ cookie?: never;
4870
+ };
3892
4871
  /**
3893
- * Register a webhook subscription
3894
- * @description Subscribe a URL to a list of event types and (optionally) a line allowlist. The server signs deliveries with an HMAC-SHA256 secret returned once here. Retries follow exponential backoff; 10 consecutive failures auto-disable the subscription. Accepts an API key (`isnap_*`) or a dashboard JWT.
4872
+ * Replay webhook events within the retention window
4873
+ * @description Pull events fired in the last 72h (the retention window) so a partner whose receiver was down can reconcile. Cursor-paginated on `(created_at DESC, event_id DESC)`. Each item is the same `§4.1` envelope the live webhook delivery carried partners deduplicate by `event_id`.
3895
4874
  */
3896
- post: {
4875
+ get: {
3897
4876
  parameters: {
3898
- query?: never;
4877
+ query: {
4878
+ since: string;
4879
+ until?: string;
4880
+ /** @description A concrete event type (e.g. `message.received`), a family wildcard (`line.*`), or the catch-all `*`. */
4881
+ event?: components["schemas"]["WebhookEventName"];
4882
+ line_id?: string;
4883
+ limit?: number;
4884
+ cursor?: string;
4885
+ };
3899
4886
  header?: never;
3900
4887
  path?: never;
3901
4888
  cookie?: never;
3902
4889
  };
3903
- requestBody?: {
3904
- content: {
3905
- "application/json": {
3906
- /** Format: uri */
3907
- url: string;
3908
- events: components["schemas"]["WebhookEventName"][];
3909
- line_ids?: string[];
3910
- };
3911
- };
3912
- };
4890
+ requestBody?: never;
3913
4891
  responses: {
3914
- /** @description Webhook created */
3915
- 201: {
4892
+ /** @description Webhook events (paginated, §4.1 envelope items) */
4893
+ 200: {
3916
4894
  headers: {
3917
4895
  [name: string]: unknown;
3918
4896
  };
3919
4897
  content: {
3920
- "application/json": {
3921
- /** @enum {boolean} */
3922
- success: true;
3923
- data: components["schemas"]["Webhook"];
3924
- trace_id: string;
3925
- request_id: string;
3926
- };
4898
+ "application/json": components["schemas"]["WebhookEventsEnvelope"];
3927
4899
  };
3928
4900
  };
3929
4901
  /** @description Bad request — validation failed */
@@ -3964,24 +4936,28 @@ export interface paths {
3964
4936
  };
3965
4937
  };
3966
4938
  };
4939
+ put?: never;
4940
+ post?: never;
3967
4941
  delete?: never;
3968
4942
  options?: never;
3969
4943
  head?: never;
3970
4944
  patch?: never;
3971
4945
  trace?: never;
3972
4946
  };
3973
- "/v1/webhooks/{id}": {
4947
+ "/v1/webhooks/{id}/rotate-secret": {
3974
4948
  parameters: {
3975
4949
  query?: never;
3976
4950
  header?: never;
3977
4951
  path?: never;
3978
4952
  cookie?: never;
3979
4953
  };
4954
+ get?: never;
4955
+ put?: never;
3980
4956
  /**
3981
- * Get a webhook subscription
3982
- * @description Fetch a single webhook subscription by its `wh_<base62>` id. Returns the URL, the event list, the line allowlist, the active flag, and the metadata never the secret (rotate via the dedicated endpoint to mint a new one).
4957
+ * Rotate the signing secret
4958
+ * @description Generate a new HMAC signing secret. The returned plaintext is shown once update your verifier promptly. The old secret remains valid for a short grace window (`old_secret_expires_at`) so in-flight deliveries verify against either secret.
3983
4959
  */
3984
- get: {
4960
+ post: {
3985
4961
  parameters: {
3986
4962
  query?: never;
3987
4963
  header?: never;
@@ -3992,13 +4968,13 @@ export interface paths {
3992
4968
  };
3993
4969
  requestBody?: never;
3994
4970
  responses: {
3995
- /** @description Webhook */
4971
+ /** @description Secret rotated — plaintext shown once */
3996
4972
  200: {
3997
4973
  headers: {
3998
4974
  [name: string]: unknown;
3999
4975
  };
4000
4976
  content: {
4001
- "application/json": components["schemas"]["WebhookV1Envelope"];
4977
+ "application/json": components["schemas"]["WebhookRotateSecretEnvelope"];
4002
4978
  };
4003
4979
  };
4004
4980
  /** @description Bad request — validation failed */
@@ -4048,15 +5024,33 @@ export interface paths {
4048
5024
  };
4049
5025
  };
4050
5026
  };
4051
- put?: never;
4052
- post?: never;
5027
+ delete?: never;
5028
+ options?: never;
5029
+ head?: never;
5030
+ patch?: never;
5031
+ trace?: never;
5032
+ };
5033
+ "/v1/webhooks/{id}/deliveries": {
5034
+ parameters: {
5035
+ query?: never;
5036
+ header?: never;
5037
+ path?: never;
5038
+ cookie?: never;
5039
+ };
4053
5040
  /**
4054
- * Delete a webhook subscription
4055
- * @description Permanently delete the webhook subscription. In-flight deliveries already enqueued will still attempt the deletion only stops new event matching. Accepts an API key (`isnap_*`) or a dashboard JWT.
5041
+ * List delivery attempts for a webhook
5042
+ * @description Inspect delivery attempts for a webhook subscription. Cursor-paginated on `(createdAt DESC, id DESC)`; filter by status (`pending` / `success` / `failed`), event type, or time window. Each row carries the request snippet, the response status + body snippet, and the next-retry timestamp.
4056
5043
  */
4057
- delete: {
5044
+ get: {
4058
5045
  parameters: {
4059
- query?: never;
5046
+ query?: {
5047
+ limit?: number;
5048
+ cursor?: string;
5049
+ status?: "pending" | "success" | "failed";
5050
+ event?: string;
5051
+ since?: string;
5052
+ until?: string;
5053
+ };
4060
5054
  header?: never;
4061
5055
  path: {
4062
5056
  id: string;
@@ -4065,12 +5059,14 @@ export interface paths {
4065
5059
  };
4066
5060
  requestBody?: never;
4067
5061
  responses: {
4068
- /** @description Webhook deleted */
4069
- 204: {
5062
+ /** @description Delivery list (cursor paginated) */
5063
+ 200: {
4070
5064
  headers: {
4071
5065
  [name: string]: unknown;
4072
5066
  };
4073
- content?: never;
5067
+ content: {
5068
+ "application/json": components["schemas"]["WebhookDeliveryListEnvelope"];
5069
+ };
4074
5070
  };
4075
5071
  /** @description Bad request — validation failed */
4076
5072
  400: {
@@ -4119,41 +5115,46 @@ export interface paths {
4119
5115
  };
4120
5116
  };
4121
5117
  };
5118
+ put?: never;
5119
+ post?: never;
5120
+ delete?: never;
4122
5121
  options?: never;
4123
5122
  head?: never;
5123
+ patch?: never;
5124
+ trace?: never;
5125
+ };
5126
+ "/v1/webhooks/{id}/deliveries/{delivery_id}/replay": {
5127
+ parameters: {
5128
+ query?: never;
5129
+ header?: never;
5130
+ path?: never;
5131
+ cookie?: never;
5132
+ };
5133
+ get?: never;
5134
+ put?: never;
4124
5135
  /**
4125
- * Update a webhook subscription
4126
- * @description Partial update of a webhook subscription. Any subset of `url`, `events`, `line_ids`, `description`, and `active` is accepted; at least one field is required. Event names are validated against the shared `WebhookEventName` enum so typos reject at 400.
5136
+ * Replay a delivery attempt
5137
+ * @description Re-enqueue a prior delivery attempt. Mints a new `whd_` delivery row referencing the original via `replay_of` but keeps the same `event_id` so partner-side dedup collapses both into the same logical event.
4127
5138
  */
4128
- patch: {
5139
+ post: {
4129
5140
  parameters: {
4130
5141
  query?: never;
4131
5142
  header?: never;
4132
5143
  path: {
4133
5144
  id: string;
5145
+ delivery_id: string;
4134
5146
  };
4135
5147
  cookie?: never;
4136
5148
  };
4137
- requestBody?: {
4138
- content: {
4139
- "application/json": {
4140
- /** Format: uri */
4141
- url?: string;
4142
- events?: components["schemas"]["WebhookEventName"][];
4143
- line_ids?: string[] | null;
4144
- description?: string | null;
4145
- active?: boolean;
4146
- };
4147
- };
4148
- };
5149
+ requestBody?: never;
4149
5150
  responses: {
4150
- /** @description Webhook updated */
4151
- 200: {
5151
+ /** @description Replay queued */
5152
+ 202: {
4152
5153
  headers: {
4153
5154
  [name: string]: unknown;
4154
5155
  };
4155
5156
  content: {
4156
- "application/json": components["schemas"]["WebhookV1Envelope"];
5157
+ "application/json": components["schemas"]["WebhookDeliveryReplayEnvelope"];
4157
5158
  };
4158
5159
  };
4159
5160
  /** @description Bad request — validation failed */
@@ -4203,43 +5204,51 @@ export interface paths {
4203
5204
  };
4204
5205
  };
4205
5206
  };
5207
+ delete?: never;
5208
+ options?: never;
5209
+ head?: never;
5210
+ patch?: never;
4206
5211
  trace?: never;
4207
5212
  };
4208
- "/v1/webhooks/events": {
5213
+ "/v1/attachments": {
4209
5214
  parameters: {
4210
5215
  query?: never;
4211
5216
  header?: never;
4212
5217
  path?: never;
4213
5218
  cookie?: never;
4214
5219
  };
5220
+ get?: never;
5221
+ put?: never;
4215
5222
  /**
4216
- * Replay webhook events within the retention window
4217
- * @description Pull events fired in the last 72h (the retention window) so a partner whose receiver was down can reconcile. Cursor-paginated on `(created_at DESC, event_id DESC)`. Each item is the same `§4.1` envelope the live webhook delivery carried partners deduplicate by `event_id`.
5223
+ * Create an attachment + receive a signed upload URL
5224
+ * @description Mint a new attachment record and receive a presigned R2 PUT URL valid for 15 minutes. The client uploads the bytes directly to R2 (the server never proxies them) and then references the returned `id` in the `attachment_ids` array on `POST /v1/messages`. Hard cap: 5 GB (R2 single-PUT ceiling). Attachments larger than 100 MB return a non-blocking `large_attachment_may_delay_delivery` warning.
4218
5225
  */
4219
- get: {
5226
+ post: {
4220
5227
  parameters: {
4221
- query: {
4222
- since: string;
4223
- until?: string;
4224
- /** @description A concrete event type (e.g. `message.received`), a family wildcard (`line.*`), or the catch-all `*`. */
4225
- event?: components["schemas"]["WebhookEventName"];
4226
- line_id?: string;
4227
- limit?: number;
4228
- cursor?: string;
4229
- };
5228
+ query?: never;
4230
5229
  header?: never;
4231
5230
  path?: never;
4232
5231
  cookie?: never;
4233
5232
  };
4234
- requestBody?: never;
5233
+ requestBody?: {
5234
+ content: {
5235
+ "application/json": components["schemas"]["CreateAttachment"];
5236
+ };
5237
+ };
4235
5238
  responses: {
4236
- /** @description Webhook events (paginated, §4.1 envelope items) */
4237
- 200: {
5239
+ /** @description Attachment row created, upload pending */
5240
+ 201: {
4238
5241
  headers: {
4239
5242
  [name: string]: unknown;
4240
5243
  };
4241
5244
  content: {
4242
- "application/json": components["schemas"]["WebhookEventsEnvelope"];
5245
+ "application/json": {
5246
+ /** @enum {boolean} */
5247
+ success: true;
5248
+ data: components["schemas"]["CreateAttachmentResponse"];
5249
+ trace_id: string;
5250
+ request_id: string;
5251
+ };
4243
5252
  };
4244
5253
  };
4245
5254
  /** @description Bad request — validation failed */
@@ -4280,28 +5289,24 @@ export interface paths {
4280
5289
  };
4281
5290
  };
4282
5291
  };
4283
- put?: never;
4284
- post?: never;
4285
5292
  delete?: never;
4286
5293
  options?: never;
4287
5294
  head?: never;
4288
5295
  patch?: never;
4289
5296
  trace?: never;
4290
5297
  };
4291
- "/v1/webhooks/{id}/rotate-secret": {
5298
+ "/v1/attachments/{id}": {
4292
5299
  parameters: {
4293
5300
  query?: never;
4294
5301
  header?: never;
4295
5302
  path?: never;
4296
5303
  cookie?: never;
4297
5304
  };
4298
- get?: never;
4299
- put?: never;
4300
5305
  /**
4301
- * Rotate the signing secret
4302
- * @description Generate a new HMAC signing secret. The returned plaintext is shown once update your verifier promptly. The old secret remains valid for a short grace window (`old_secret_expires_at`) so in-flight deliveries verify against either secret.
5306
+ * Get an attachment + signed download URL
5307
+ * @description Fetch an attachment record by its `att_<base62>` id. The response includes a short-TTL signed R2 download URL re-call this endpoint to refresh the URL when the previous one expires.
4303
5308
  */
4304
- post: {
5309
+ get: {
4305
5310
  parameters: {
4306
5311
  query?: never;
4307
5312
  header?: never;
@@ -4312,13 +5317,19 @@ export interface paths {
4312
5317
  };
4313
5318
  requestBody?: never;
4314
5319
  responses: {
4315
- /** @description Secret rotated plaintext shown once */
5320
+ /** @description Attachment record + fresh signed download URL */
4316
5321
  200: {
4317
5322
  headers: {
4318
5323
  [name: string]: unknown;
4319
5324
  };
4320
5325
  content: {
4321
- "application/json": components["schemas"]["WebhookRotateSecretEnvelope"];
5326
+ "application/json": {
5327
+ /** @enum {boolean} */
5328
+ success: true;
5329
+ data: components["schemas"]["Attachment"];
5330
+ trace_id: string;
5331
+ request_id: string;
5332
+ };
4322
5333
  };
4323
5334
  };
4324
5335
  /** @description Bad request — validation failed */
@@ -4368,61 +5379,60 @@ export interface paths {
4368
5379
  };
4369
5380
  };
4370
5381
  };
5382
+ put?: never;
5383
+ post?: never;
4371
5384
  delete?: never;
4372
5385
  options?: never;
4373
5386
  head?: never;
4374
5387
  patch?: never;
4375
5388
  trace?: never;
4376
5389
  };
4377
- "/v1/webhooks/{id}/deliveries": {
5390
+ "/v1/lookup": {
4378
5391
  parameters: {
4379
5392
  query?: never;
4380
5393
  header?: never;
4381
5394
  path?: never;
4382
5395
  cookie?: never;
4383
5396
  };
5397
+ get?: never;
5398
+ put?: never;
4384
5399
  /**
4385
- * List delivery attempts for a webhook
4386
- * @description Inspect delivery attempts for a webhook subscription. Cursor-paginated on `(createdAt DESC, id DESC)`; filter by status (`pending` / `success` / `failed`), event type, or time window. Each row carries the request snippet, the response status + body snippet, and the next-retry timestamp.
5400
+ * Look up a recipient capability
5401
+ * @description Check whether a phone number or email is iMessage-capable. Returns `imessage: true | false | null`; `null` means we have no cached probe result yet and the caller should fall back to SMS-safe behaviour. Rate-limited per line and per account.
4387
5402
  */
4388
- get: {
5403
+ post: {
4389
5404
  parameters: {
4390
- query?: {
4391
- limit?: number;
4392
- cursor?: string;
4393
- status?: "pending" | "success" | "failed";
4394
- event?: string;
4395
- since?: string;
4396
- until?: string;
4397
- };
5405
+ query?: never;
4398
5406
  header?: never;
4399
- path: {
4400
- id: string;
4401
- };
5407
+ path?: never;
4402
5408
  cookie?: never;
4403
5409
  };
4404
- requestBody?: never;
5410
+ requestBody?: {
5411
+ content: {
5412
+ "application/json": components["schemas"]["LookupRequest"];
5413
+ };
5414
+ };
4405
5415
  responses: {
4406
- /** @description Delivery list (cursor paginated) */
5416
+ /** @description Lookup result cache hit, fast probe, or an un-probed miss */
4407
5417
  200: {
4408
5418
  headers: {
4409
5419
  [name: string]: unknown;
4410
5420
  };
4411
5421
  content: {
4412
- "application/json": components["schemas"]["WebhookDeliveryListEnvelope"];
5422
+ "application/json": components["schemas"]["LookupEnvelope"];
4413
5423
  };
4414
5424
  };
4415
- /** @description Bad requestvalidation failed */
4416
- 400: {
5425
+ /** @description Lookup pendinga device probe was dispatched but had not answered within the wait budget; re-poll the same request after `retry_after` seconds */
5426
+ 202: {
4417
5427
  headers: {
4418
5428
  [name: string]: unknown;
4419
5429
  };
4420
5430
  content: {
4421
- "application/json": components["schemas"]["ErrorEnvelope"];
5431
+ "application/json": components["schemas"]["LookupEnvelope"];
4422
5432
  };
4423
5433
  };
4424
- /** @description Unauthenticatedmissing or invalid bearer token */
4425
- 401: {
5434
+ /** @description Bad request validation failed */
5435
+ 400: {
4426
5436
  headers: {
4427
5437
  [name: string]: unknown;
4428
5438
  };
@@ -4430,8 +5440,8 @@ export interface paths {
4430
5440
  "application/json": components["schemas"]["ErrorEnvelope"];
4431
5441
  };
4432
5442
  };
4433
- /** @description Not found */
4434
- 404: {
5443
+ /** @description Unauthenticated missing or invalid bearer token */
5444
+ 401: {
4435
5445
  headers: {
4436
5446
  [name: string]: unknown;
4437
5447
  };
@@ -4459,46 +5469,42 @@ export interface paths {
4459
5469
  };
4460
5470
  };
4461
5471
  };
4462
- put?: never;
4463
- post?: never;
4464
5472
  delete?: never;
4465
5473
  options?: never;
4466
5474
  head?: never;
4467
5475
  patch?: never;
4468
5476
  trace?: never;
4469
5477
  };
4470
- "/v1/webhooks/{id}/deliveries/{delivery_id}/replay": {
5478
+ "/v1/area-codes": {
4471
5479
  parameters: {
4472
5480
  query?: never;
4473
5481
  header?: never;
4474
5482
  path?: never;
4475
5483
  cookie?: never;
4476
5484
  };
4477
- get?: never;
4478
- put?: never;
4479
5485
  /**
4480
- * Replay a delivery attempt
4481
- * @description Re-enqueue a prior delivery attempt. Mints a new `whd_` delivery row referencing the original via `replay_of` but keeps the same `event_id` so partner-side dedup collapses both into the same logical event.
5486
+ * List selectable area codes (public)
5487
+ * @description Enumerate the area codes a customer can request as `preferred_area_codes` on `POST /v1/pre-orders`, for rendering a pre-order area-code picker. Optional `?country=US` filters the list. Also returns the selectable countries so the same response can drive a country dropdown. Unauthenticated — mirrors `GET /v1/lines` and `GET /pricing`. The list is the *selectable* set (a curated subset of NANP), not a guarantee of immediate inventory: pre-orders are precisely for tiers with no in-stock line.
4482
5488
  */
4483
- post: {
5489
+ get: {
4484
5490
  parameters: {
4485
- query?: never;
4486
- header?: never;
4487
- path: {
4488
- id: string;
4489
- delivery_id: string;
5491
+ query?: {
5492
+ /** @description ISO 3166-1 alpha-2 country filter (case-insensitive). Omit for all countries. */
5493
+ country?: string;
4490
5494
  };
5495
+ header?: never;
5496
+ path?: never;
4491
5497
  cookie?: never;
4492
5498
  };
4493
5499
  requestBody?: never;
4494
5500
  responses: {
4495
- /** @description Replay queued */
4496
- 202: {
5501
+ /** @description Selectable area codes + countries */
5502
+ 200: {
4497
5503
  headers: {
4498
5504
  [name: string]: unknown;
4499
5505
  };
4500
5506
  content: {
4501
- "application/json": components["schemas"]["WebhookDeliveryReplayEnvelope"];
5507
+ "application/json": components["schemas"]["AreaCodesEnvelope"];
4502
5508
  };
4503
5509
  };
4504
5510
  /** @description Bad request — validation failed */
@@ -4510,24 +5516,6 @@ export interface paths {
4510
5516
  "application/json": components["schemas"]["ErrorEnvelope"];
4511
5517
  };
4512
5518
  };
4513
- /** @description Unauthenticated — missing or invalid bearer token */
4514
- 401: {
4515
- headers: {
4516
- [name: string]: unknown;
4517
- };
4518
- content: {
4519
- "application/json": components["schemas"]["ErrorEnvelope"];
4520
- };
4521
- };
4522
- /** @description Not found */
4523
- 404: {
4524
- headers: {
4525
- [name: string]: unknown;
4526
- };
4527
- content: {
4528
- "application/json": components["schemas"]["ErrorEnvelope"];
4529
- };
4530
- };
4531
5519
  /** @description Too many requests — rate limit exceeded */
4532
5520
  429: {
4533
5521
  headers: {
@@ -4548,51 +5536,47 @@ export interface paths {
4548
5536
  };
4549
5537
  };
4550
5538
  };
5539
+ put?: never;
5540
+ post?: never;
4551
5541
  delete?: never;
4552
5542
  options?: never;
4553
5543
  head?: never;
4554
5544
  patch?: never;
4555
5545
  trace?: never;
4556
5546
  };
4557
- "/v1/attachments": {
5547
+ "/v1/chats": {
4558
5548
  parameters: {
4559
5549
  query?: never;
4560
5550
  header?: never;
4561
5551
  path?: never;
4562
5552
  cookie?: never;
4563
5553
  };
4564
- get?: never;
4565
- put?: never;
4566
5554
  /**
4567
- * Create an attachment + receive a signed upload URL
4568
- * @description Mint a new attachment record and receive a presigned R2 PUT URL valid for 15 minutes. The client uploads the bytes directly to R2 (the server never proxies them) and then references the returned `id` in the `attachment_ids` array on `POST /v1/messages`. Hard cap: 5 GB (R2 single-PUT ceiling). Attachments larger than 100 MB return a non-blocking `large_attachment_may_delay_delivery` warning.
5555
+ * List chats
5556
+ * @description Cursor-paginated list of the caller's conversations, newest activity first. Filter by `line_id`. On shared-plan (virtual) lines each chat carries `outbound_number` — the physical number currently serving that contact (null once the binding is released; the next outbound is assigned a fresh number). Non-shared lines always carry `outbound_number: null`. Accepts an API key (`isnap_*`) or a dashboard JWT; line-scoped API keys only see chats of the lines in their allowlist.
4569
5557
  */
4570
- post: {
5558
+ get: {
4571
5559
  parameters: {
4572
- query?: never;
5560
+ query?: {
5561
+ line_id?: string;
5562
+ limit?: number;
5563
+ cursor?: string;
5564
+ since?: string;
5565
+ until?: string;
5566
+ };
4573
5567
  header?: never;
4574
5568
  path?: never;
4575
5569
  cookie?: never;
4576
5570
  };
4577
- requestBody?: {
4578
- content: {
4579
- "application/json": components["schemas"]["CreateAttachment"];
4580
- };
4581
- };
5571
+ requestBody?: never;
4582
5572
  responses: {
4583
- /** @description Attachment row created, upload pending */
4584
- 201: {
5573
+ /** @description Chat list */
5574
+ 200: {
4585
5575
  headers: {
4586
5576
  [name: string]: unknown;
4587
5577
  };
4588
5578
  content: {
4589
- "application/json": {
4590
- /** @enum {boolean} */
4591
- success: true;
4592
- data: components["schemas"]["CreateAttachmentResponse"];
4593
- trace_id: string;
4594
- request_id: string;
4595
- };
5579
+ "application/json": components["schemas"]["ChatListEnvelope"];
4596
5580
  };
4597
5581
  };
4598
5582
  /** @description Bad request — validation failed */
@@ -4633,13 +5617,15 @@ export interface paths {
4633
5617
  };
4634
5618
  };
4635
5619
  };
5620
+ put?: never;
5621
+ post?: never;
4636
5622
  delete?: never;
4637
5623
  options?: never;
4638
5624
  head?: never;
4639
5625
  patch?: never;
4640
5626
  trace?: never;
4641
5627
  };
4642
- "/v1/attachments/{id}": {
5628
+ "/v1/chats/{chat_id}": {
4643
5629
  parameters: {
4644
5630
  query?: never;
4645
5631
  header?: never;
@@ -4647,33 +5633,27 @@ export interface paths {
4647
5633
  cookie?: never;
4648
5634
  };
4649
5635
  /**
4650
- * Get an attachment + signed download URL
4651
- * @description Fetch an attachment record by its `att_<base62>` id. The response includes a short-TTL signed R2 download URL — re-call this endpoint to refresh the URL when the previous one expires.
5636
+ * Get a chat
5637
+ * @description Fetch a single conversation by its `chat_<base62>` id. Same shape as the list items, including `outbound_number` for shared-plan lines. Cross-account and out-of-scope ids resolve 404 (no existence leak).
4652
5638
  */
4653
5639
  get: {
4654
5640
  parameters: {
4655
5641
  query?: never;
4656
5642
  header?: never;
4657
5643
  path: {
4658
- id: string;
5644
+ chat_id: string;
4659
5645
  };
4660
5646
  cookie?: never;
4661
5647
  };
4662
5648
  requestBody?: never;
4663
5649
  responses: {
4664
- /** @description Attachment record + fresh signed download URL */
5650
+ /** @description Chat */
4665
5651
  200: {
4666
5652
  headers: {
4667
5653
  [name: string]: unknown;
4668
5654
  };
4669
5655
  content: {
4670
- "application/json": {
4671
- /** @enum {boolean} */
4672
- success: true;
4673
- data: components["schemas"]["Attachment"];
4674
- trace_id: string;
4675
- request_id: string;
4676
- };
5656
+ "application/json": components["schemas"]["ChatEnvelope"];
4677
5657
  };
4678
5658
  };
4679
5659
  /** @description Bad request — validation failed */
@@ -4731,7 +5711,7 @@ export interface paths {
4731
5711
  patch?: never;
4732
5712
  trace?: never;
4733
5713
  };
4734
- "/v1/lookup": {
5714
+ "/v1/chats/{chat_id}/share-contact-card": {
4735
5715
  parameters: {
4736
5716
  query?: never;
4737
5717
  header?: never;
@@ -4740,43 +5720,50 @@ export interface paths {
4740
5720
  };
4741
5721
  get?: never;
4742
5722
  put?: never;
4743
- /**
4744
- * Look up a recipient capability
4745
- * @description Check whether a phone number or email is iMessage-capable. Returns `imessage: true | false | null`; `null` means we have no cached probe result yet and the caller should fall back to SMS-safe behaviour. Rate-limited per line and per account.
4746
- */
4747
5723
  post: {
4748
5724
  parameters: {
4749
5725
  query?: never;
4750
5726
  header?: never;
4751
- path?: never;
5727
+ path: {
5728
+ chat_id: string;
5729
+ };
4752
5730
  cookie?: never;
4753
5731
  };
4754
5732
  requestBody?: {
4755
5733
  content: {
4756
- "application/json": components["schemas"]["LookupRequest"];
5734
+ "application/json": {
5735
+ first_name: string;
5736
+ last_name?: string;
5737
+ /** Format: uri */
5738
+ avatar_url?: string;
5739
+ phone_numbers?: string[];
5740
+ emails?: string[];
5741
+ organization?: string;
5742
+ note?: string;
5743
+ };
4757
5744
  };
4758
5745
  };
4759
5746
  responses: {
4760
- /** @description Lookup result cache hit, fast probe, or an un-probed miss */
4761
- 200: {
5747
+ /** @description Contact card queued */
5748
+ 202: {
4762
5749
  headers: {
4763
5750
  [name: string]: unknown;
4764
5751
  };
4765
5752
  content: {
4766
- "application/json": components["schemas"]["LookupEnvelope"];
5753
+ "application/json": components["schemas"]["ShareContactCardEnvelope"];
4767
5754
  };
4768
5755
  };
4769
- /** @description Lookup pendinga device probe was dispatched but had not answered within the wait budget; re-poll the same request after `retry_after` seconds */
4770
- 202: {
5756
+ /** @description Bad requestvalidation failed */
5757
+ 400: {
4771
5758
  headers: {
4772
5759
  [name: string]: unknown;
4773
5760
  };
4774
5761
  content: {
4775
- "application/json": components["schemas"]["LookupEnvelope"];
5762
+ "application/json": components["schemas"]["ErrorEnvelope"];
4776
5763
  };
4777
5764
  };
4778
- /** @description Bad request validation failed */
4779
- 400: {
5765
+ /** @description Unauthenticatedmissing or invalid bearer token */
5766
+ 401: {
4780
5767
  headers: {
4781
5768
  [name: string]: unknown;
4782
5769
  };
@@ -4784,8 +5771,26 @@ export interface paths {
4784
5771
  "application/json": components["schemas"]["ErrorEnvelope"];
4785
5772
  };
4786
5773
  };
4787
- /** @description Unauthenticatedmissing or invalid bearer token */
4788
- 401: {
5774
+ /** @description Forbiddencaller authenticated but not allowed */
5775
+ 403: {
5776
+ headers: {
5777
+ [name: string]: unknown;
5778
+ };
5779
+ content: {
5780
+ "application/json": components["schemas"]["ErrorEnvelope"];
5781
+ };
5782
+ };
5783
+ /** @description Not found */
5784
+ 404: {
5785
+ headers: {
5786
+ [name: string]: unknown;
5787
+ };
5788
+ content: {
5789
+ "application/json": components["schemas"]["ErrorEnvelope"];
5790
+ };
5791
+ };
5792
+ /** @description Unprocessable — domain rule violation */
5793
+ 422: {
4789
5794
  headers: {
4790
5795
  [name: string]: unknown;
4791
5796
  };
@@ -4819,49 +5824,41 @@ export interface paths {
4819
5824
  patch?: never;
4820
5825
  trace?: never;
4821
5826
  };
4822
- "/v1/area-codes": {
5827
+ "/v1/chats/{chat_id}/typing": {
4823
5828
  parameters: {
4824
5829
  query?: never;
4825
5830
  header?: never;
4826
5831
  path?: never;
4827
5832
  cookie?: never;
4828
5833
  };
4829
- /**
4830
- * List selectable area codes (public)
4831
- * @description Enumerate the area codes a customer can request as `preferred_area_codes` on `POST /v1/pre-orders`, for rendering a pre-order area-code picker. Optional `?country=US` filters the list. Also returns the selectable countries so the same response can drive a country dropdown. Unauthenticated — mirrors `GET /v1/lines` and `GET /pricing`. The list is the *selectable* set (a curated subset of NANP), not a guarantee of immediate inventory: pre-orders are precisely for tiers with no in-stock line.
4832
- */
4833
- get: {
5834
+ get?: never;
5835
+ put?: never;
5836
+ post: {
4834
5837
  parameters: {
4835
- query?: {
4836
- /** @description ISO 3166-1 alpha-2 country filter (case-insensitive). Omit for all countries. */
4837
- country?: string;
4838
- };
5838
+ query?: never;
4839
5839
  header?: never;
4840
- path?: never;
5840
+ path: {
5841
+ chat_id: string;
5842
+ };
4841
5843
  cookie?: never;
4842
5844
  };
4843
- requestBody?: never;
4844
- responses: {
4845
- /** @description Selectable area codes + countries */
4846
- 200: {
4847
- headers: {
4848
- [name: string]: unknown;
4849
- };
4850
- content: {
4851
- "application/json": components["schemas"]["AreaCodesEnvelope"];
4852
- };
5845
+ requestBody?: {
5846
+ content: {
5847
+ "application/json": Record<string, never>;
4853
5848
  };
4854
- /** @description Bad request — validation failed */
4855
- 400: {
5849
+ };
5850
+ responses: {
5851
+ /** @description Typing indicator started */
5852
+ 202: {
4856
5853
  headers: {
4857
5854
  [name: string]: unknown;
4858
5855
  };
4859
5856
  content: {
4860
- "application/json": components["schemas"]["ErrorEnvelope"];
5857
+ "application/json": components["schemas"]["ChatTypingStartEnvelope"];
4861
5858
  };
4862
- };
4863
- /** @description Too many requests rate limit exceeded */
4864
- 429: {
5859
+ };
5860
+ /** @description Bad requestvalidation failed */
5861
+ 400: {
4865
5862
  headers: {
4866
5863
  [name: string]: unknown;
4867
5864
  };
@@ -4869,8 +5866,8 @@ export interface paths {
4869
5866
  "application/json": components["schemas"]["ErrorEnvelope"];
4870
5867
  };
4871
5868
  };
4872
- /** @description Internal server error */
4873
- 500: {
5869
+ /** @description Unauthenticated missing or invalid bearer token */
5870
+ 401: {
4874
5871
  headers: {
4875
5872
  [name: string]: unknown;
4876
5873
  };
@@ -4878,53 +5875,17 @@ export interface paths {
4878
5875
  "application/json": components["schemas"]["ErrorEnvelope"];
4879
5876
  };
4880
5877
  };
4881
- };
4882
- };
4883
- put?: never;
4884
- post?: never;
4885
- delete?: never;
4886
- options?: never;
4887
- head?: never;
4888
- patch?: never;
4889
- trace?: never;
4890
- };
4891
- "/v1/chats": {
4892
- parameters: {
4893
- query?: never;
4894
- header?: never;
4895
- path?: never;
4896
- cookie?: never;
4897
- };
4898
- /**
4899
- * List chats
4900
- * @description Cursor-paginated list of the caller's conversations, newest activity first. Filter by `line_id`. On shared-plan (virtual) lines each chat carries `outbound_number` — the physical number currently serving that contact (null once the binding is released; the next outbound is assigned a fresh number). Non-shared lines always carry `outbound_number: null`. Accepts an API key (`isnap_*`) or a dashboard JWT; line-scoped API keys only see chats of the lines in their allowlist.
4901
- */
4902
- get: {
4903
- parameters: {
4904
- query?: {
4905
- line_id?: string;
4906
- limit?: number;
4907
- cursor?: string;
4908
- since?: string;
4909
- until?: string;
4910
- };
4911
- header?: never;
4912
- path?: never;
4913
- cookie?: never;
4914
- };
4915
- requestBody?: never;
4916
- responses: {
4917
- /** @description Chat list */
4918
- 200: {
5878
+ /** @description Forbidden — caller authenticated but not allowed */
5879
+ 403: {
4919
5880
  headers: {
4920
5881
  [name: string]: unknown;
4921
5882
  };
4922
5883
  content: {
4923
- "application/json": components["schemas"]["ChatListEnvelope"];
5884
+ "application/json": components["schemas"]["ErrorEnvelope"];
4924
5885
  };
4925
5886
  };
4926
- /** @description Bad request — validation failed */
4927
- 400: {
5887
+ /** @description Not found */
5888
+ 404: {
4928
5889
  headers: {
4929
5890
  [name: string]: unknown;
4930
5891
  };
@@ -4932,8 +5893,8 @@ export interface paths {
4932
5893
  "application/json": components["schemas"]["ErrorEnvelope"];
4933
5894
  };
4934
5895
  };
4935
- /** @description Unauthenticatedmissing or invalid bearer token */
4936
- 401: {
5896
+ /** @description Unprocessabledomain rule violation */
5897
+ 422: {
4937
5898
  headers: {
4938
5899
  [name: string]: unknown;
4939
5900
  };
@@ -4961,26 +5922,7 @@ export interface paths {
4961
5922
  };
4962
5923
  };
4963
5924
  };
4964
- put?: never;
4965
- post?: never;
4966
- delete?: never;
4967
- options?: never;
4968
- head?: never;
4969
- patch?: never;
4970
- trace?: never;
4971
- };
4972
- "/v1/chats/{chat_id}": {
4973
- parameters: {
4974
- query?: never;
4975
- header?: never;
4976
- path?: never;
4977
- cookie?: never;
4978
- };
4979
- /**
4980
- * Get a chat
4981
- * @description Fetch a single conversation by its `chat_<base62>` id. Same shape as the list items, including `outbound_number` for shared-plan lines. Cross-account and out-of-scope ids resolve 404 (no existence leak).
4982
- */
4983
- get: {
5925
+ delete: {
4984
5926
  parameters: {
4985
5927
  query?: never;
4986
5928
  header?: never;
@@ -4991,13 +5933,13 @@ export interface paths {
4991
5933
  };
4992
5934
  requestBody?: never;
4993
5935
  responses: {
4994
- /** @description Chat */
5936
+ /** @description Typing indicator stopped */
4995
5937
  200: {
4996
5938
  headers: {
4997
5939
  [name: string]: unknown;
4998
5940
  };
4999
5941
  content: {
5000
- "application/json": components["schemas"]["ChatEnvelope"];
5942
+ "application/json": components["schemas"]["ChatTypingStopEnvelope"];
5001
5943
  };
5002
5944
  };
5003
5945
  /** @description Bad request — validation failed */
@@ -5018,6 +5960,15 @@ export interface paths {
5018
5960
  "application/json": components["schemas"]["ErrorEnvelope"];
5019
5961
  };
5020
5962
  };
5963
+ /** @description Forbidden — caller authenticated but not allowed */
5964
+ 403: {
5965
+ headers: {
5966
+ [name: string]: unknown;
5967
+ };
5968
+ content: {
5969
+ "application/json": components["schemas"]["ErrorEnvelope"];
5970
+ };
5971
+ };
5021
5972
  /** @description Not found */
5022
5973
  404: {
5023
5974
  headers: {
@@ -5047,15 +5998,12 @@ export interface paths {
5047
5998
  };
5048
5999
  };
5049
6000
  };
5050
- put?: never;
5051
- post?: never;
5052
- delete?: never;
5053
6001
  options?: never;
5054
6002
  head?: never;
5055
6003
  patch?: never;
5056
6004
  trace?: never;
5057
6005
  };
5058
- "/v1/chats/{chat_id}/share-contact-card": {
6006
+ "/v1/chats/{chat_id}/read": {
5059
6007
  parameters: {
5060
6008
  query?: never;
5061
6009
  header?: never;
@@ -5075,26 +6023,17 @@ export interface paths {
5075
6023
  };
5076
6024
  requestBody?: {
5077
6025
  content: {
5078
- "application/json": {
5079
- first_name: string;
5080
- last_name?: string;
5081
- /** Format: uri */
5082
- avatar_url?: string;
5083
- phone_numbers?: string[];
5084
- emails?: string[];
5085
- organization?: string;
5086
- note?: string;
5087
- };
6026
+ "application/json": Record<string, never>;
5088
6027
  };
5089
6028
  };
5090
6029
  responses: {
5091
- /** @description Contact card queued */
5092
- 202: {
6030
+ /** @description Chat marked read */
6031
+ 200: {
5093
6032
  headers: {
5094
6033
  [name: string]: unknown;
5095
6034
  };
5096
6035
  content: {
5097
- "application/json": components["schemas"]["ShareContactCardEnvelope"];
6036
+ "application/json": components["schemas"]["ChatMarkReadEnvelope"];
5098
6037
  };
5099
6038
  };
5100
6039
  /** @description Bad request — validation failed */
@@ -5133,15 +6072,6 @@ export interface paths {
5133
6072
  "application/json": components["schemas"]["ErrorEnvelope"];
5134
6073
  };
5135
6074
  };
5136
- /** @description Unprocessable — domain rule violation */
5137
- 422: {
5138
- headers: {
5139
- [name: string]: unknown;
5140
- };
5141
- content: {
5142
- "application/json": components["schemas"]["ErrorEnvelope"];
5143
- };
5144
- };
5145
6075
  /** @description Too many requests — rate limit exceeded */
5146
6076
  429: {
5147
6077
  headers: {
@@ -5168,7 +6098,7 @@ export interface paths {
5168
6098
  patch?: never;
5169
6099
  trace?: never;
5170
6100
  };
5171
- "/v1/chats/{chat_id}/typing": {
6101
+ "/v1/trial/init": {
5172
6102
  parameters: {
5173
6103
  query?: never;
5174
6104
  header?: never;
@@ -5177,28 +6107,30 @@ export interface paths {
5177
6107
  };
5178
6108
  get?: never;
5179
6109
  put?: never;
6110
+ /**
6111
+ * Start a trial session
6112
+ * @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.
6113
+ */
5180
6114
  post: {
5181
6115
  parameters: {
5182
6116
  query?: never;
5183
6117
  header?: never;
5184
- path: {
5185
- chat_id: string;
5186
- };
6118
+ path?: never;
5187
6119
  cookie?: never;
5188
6120
  };
5189
6121
  requestBody?: {
5190
6122
  content: {
5191
- "application/json": Record<string, never>;
6123
+ "application/json": components["schemas"]["TrialInitBody"];
5192
6124
  };
5193
6125
  };
5194
6126
  responses: {
5195
- /** @description Typing indicator started */
5196
- 202: {
6127
+ /** @description Trial session created — pairing pending */
6128
+ 201: {
5197
6129
  headers: {
5198
6130
  [name: string]: unknown;
5199
6131
  };
5200
6132
  content: {
5201
- "application/json": components["schemas"]["ChatTypingStartEnvelope"];
6133
+ "application/json": components["schemas"]["TrialInitEnvelope"];
5202
6134
  };
5203
6135
  };
5204
6136
  /** @description Bad request — validation failed */
@@ -5219,26 +6151,8 @@ export interface paths {
5219
6151
  "application/json": components["schemas"]["ErrorEnvelope"];
5220
6152
  };
5221
6153
  };
5222
- /** @description Forbiddencaller authenticated but not allowed */
5223
- 403: {
5224
- headers: {
5225
- [name: string]: unknown;
5226
- };
5227
- content: {
5228
- "application/json": components["schemas"]["ErrorEnvelope"];
5229
- };
5230
- };
5231
- /** @description Not found */
5232
- 404: {
5233
- headers: {
5234
- [name: string]: unknown;
5235
- };
5236
- content: {
5237
- "application/json": components["schemas"]["ErrorEnvelope"];
5238
- };
5239
- };
5240
- /** @description Unprocessable — domain rule violation */
5241
- 422: {
6154
+ /** @description Conflictconcurrent or terminal state */
6155
+ 409: {
5242
6156
  headers: {
5243
6157
  [name: string]: unknown;
5244
6158
  };
@@ -5266,24 +6180,41 @@ export interface paths {
5266
6180
  };
5267
6181
  };
5268
6182
  };
5269
- delete: {
6183
+ delete?: never;
6184
+ options?: never;
6185
+ head?: never;
6186
+ patch?: never;
6187
+ trace?: never;
6188
+ };
6189
+ "/v1/trial/status": {
6190
+ parameters: {
6191
+ query?: never;
6192
+ header?: never;
6193
+ path?: never;
6194
+ cookie?: never;
6195
+ };
6196
+ /**
6197
+ * Check trial pairing status
6198
+ * @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.
6199
+ */
6200
+ get: {
5270
6201
  parameters: {
5271
- query?: never;
5272
- header?: never;
5273
- path: {
5274
- chat_id: string;
6202
+ query: {
6203
+ token: string;
5275
6204
  };
6205
+ header?: never;
6206
+ path?: never;
5276
6207
  cookie?: never;
5277
6208
  };
5278
6209
  requestBody?: never;
5279
6210
  responses: {
5280
- /** @description Typing indicator stopped */
6211
+ /** @description Current trial status */
5281
6212
  200: {
5282
6213
  headers: {
5283
6214
  [name: string]: unknown;
5284
6215
  };
5285
6216
  content: {
5286
- "application/json": components["schemas"]["ChatTypingStopEnvelope"];
6217
+ "application/json": components["schemas"]["TrialStatusEnvelope"];
5287
6218
  };
5288
6219
  };
5289
6220
  /** @description Bad request — validation failed */
@@ -5295,8 +6226,8 @@ export interface paths {
5295
6226
  "application/json": components["schemas"]["ErrorEnvelope"];
5296
6227
  };
5297
6228
  };
5298
- /** @description Unauthenticated missing or invalid bearer token */
5299
- 401: {
6229
+ /** @description Not found */
6230
+ 404: {
5300
6231
  headers: {
5301
6232
  [name: string]: unknown;
5302
6233
  };
@@ -5304,8 +6235,65 @@ export interface paths {
5304
6235
  "application/json": components["schemas"]["ErrorEnvelope"];
5305
6236
  };
5306
6237
  };
5307
- /** @description Forbidden caller authenticated but not allowed */
5308
- 403: {
6238
+ /** @description Too many requests rate limit exceeded */
6239
+ 429: {
6240
+ headers: {
6241
+ [name: string]: unknown;
6242
+ };
6243
+ content: {
6244
+ "application/json": components["schemas"]["ErrorEnvelope"];
6245
+ };
6246
+ };
6247
+ /** @description Internal server error */
6248
+ 500: {
6249
+ headers: {
6250
+ [name: string]: unknown;
6251
+ };
6252
+ content: {
6253
+ "application/json": components["schemas"]["ErrorEnvelope"];
6254
+ };
6255
+ };
6256
+ };
6257
+ };
6258
+ put?: never;
6259
+ post?: never;
6260
+ delete?: never;
6261
+ options?: never;
6262
+ head?: never;
6263
+ patch?: never;
6264
+ trace?: never;
6265
+ };
6266
+ "/v1/trial/activity": {
6267
+ parameters: {
6268
+ query?: never;
6269
+ header?: never;
6270
+ path?: never;
6271
+ cookie?: never;
6272
+ };
6273
+ /**
6274
+ * Get trial activity counters
6275
+ * @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).
6276
+ */
6277
+ get: {
6278
+ parameters: {
6279
+ query?: never;
6280
+ header?: never;
6281
+ path?: never;
6282
+ cookie?: never;
6283
+ };
6284
+ requestBody?: never;
6285
+ responses: {
6286
+ /** @description Trial activity snapshot */
6287
+ 200: {
6288
+ headers: {
6289
+ [name: string]: unknown;
6290
+ };
6291
+ content: {
6292
+ "application/json": components["schemas"]["TrialActivityEnvelope"];
6293
+ };
6294
+ };
6295
+ /** @description Bad request — validation failed */
6296
+ 400: {
5309
6297
  headers: {
5310
6298
  [name: string]: unknown;
5311
6299
  };
@@ -5313,8 +6301,8 @@ export interface paths {
5313
6301
  "application/json": components["schemas"]["ErrorEnvelope"];
5314
6302
  };
5315
6303
  };
5316
- /** @description Not found */
5317
- 404: {
6304
+ /** @description Unauthenticated missing or invalid bearer token */
6305
+ 401: {
5318
6306
  headers: {
5319
6307
  [name: string]: unknown;
5320
6308
  };
@@ -5342,12 +6330,15 @@ export interface paths {
5342
6330
  };
5343
6331
  };
5344
6332
  };
6333
+ put?: never;
6334
+ post?: never;
6335
+ delete?: never;
5345
6336
  options?: never;
5346
6337
  head?: never;
5347
6338
  patch?: never;
5348
6339
  trace?: never;
5349
6340
  };
5350
- "/v1/chats/{chat_id}/read": {
6341
+ "/v1/trial/{trial_id}/revoke": {
5351
6342
  parameters: {
5352
6343
  query?: never;
5353
6344
  header?: never;
@@ -5356,28 +6347,33 @@ export interface paths {
5356
6347
  };
5357
6348
  get?: never;
5358
6349
  put?: never;
6350
+ /**
6351
+ * Revoke a trial session
6352
+ * @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`.
6353
+ */
5359
6354
  post: {
5360
6355
  parameters: {
5361
6356
  query?: never;
5362
6357
  header?: never;
5363
6358
  path: {
5364
- chat_id: string;
6359
+ /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
6360
+ trial_id: string;
5365
6361
  };
5366
6362
  cookie?: never;
5367
6363
  };
5368
6364
  requestBody?: {
5369
6365
  content: {
5370
- "application/json": Record<string, never>;
6366
+ "application/json": components["schemas"]["TrialRevokeBody"];
5371
6367
  };
5372
6368
  };
5373
6369
  responses: {
5374
- /** @description Chat marked read */
6370
+ /** @description Trial revoked (idempotent) */
5375
6371
  200: {
5376
6372
  headers: {
5377
6373
  [name: string]: unknown;
5378
6374
  };
5379
6375
  content: {
5380
- "application/json": components["schemas"]["ChatMarkReadEnvelope"];
6376
+ "application/json": components["schemas"]["TrialRevokeEnvelope"];
5381
6377
  };
5382
6378
  };
5383
6379
  /** @description Bad request — validation failed */
@@ -5416,6 +6412,15 @@ export interface paths {
5416
6412
  "application/json": components["schemas"]["ErrorEnvelope"];
5417
6413
  };
5418
6414
  };
6415
+ /** @description Unprocessable — domain rule violation */
6416
+ 422: {
6417
+ headers: {
6418
+ [name: string]: unknown;
6419
+ };
6420
+ content: {
6421
+ "application/json": components["schemas"]["ErrorEnvelope"];
6422
+ };
6423
+ };
5419
6424
  /** @description Too many requests — rate limit exceeded */
5420
6425
  429: {
5421
6426
  headers: {
@@ -5442,7 +6447,7 @@ export interface paths {
5442
6447
  patch?: never;
5443
6448
  trace?: never;
5444
6449
  };
5445
- "/v1/trial/init": {
6450
+ "/v1/byod/pair": {
5446
6451
  parameters: {
5447
6452
  query?: never;
5448
6453
  header?: never;
@@ -5452,8 +6457,8 @@ export interface paths {
5452
6457
  get?: never;
5453
6458
  put?: never;
5454
6459
  /**
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.
6460
+ * Start a BYOD pairing flow
6461
+ * @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.
5457
6462
  */
5458
6463
  post: {
5459
6464
  parameters: {
@@ -5464,17 +6469,17 @@ export interface paths {
5464
6469
  };
5465
6470
  requestBody?: {
5466
6471
  content: {
5467
- "application/json": components["schemas"]["TrialInitBody"];
6472
+ "application/json": components["schemas"]["ByodPairBody"];
5468
6473
  };
5469
6474
  };
5470
6475
  responses: {
5471
- /** @description Trial session created pairing pending */
6476
+ /** @description Activation code + bridge download URL */
5472
6477
  201: {
5473
6478
  headers: {
5474
6479
  [name: string]: unknown;
5475
6480
  };
5476
6481
  content: {
5477
- "application/json": components["schemas"]["TrialInitEnvelope"];
6482
+ "application/json": components["schemas"]["ByodPairEnvelope"];
5478
6483
  };
5479
6484
  };
5480
6485
  /** @description Bad request — validation failed */
@@ -5495,8 +6500,8 @@ export interface paths {
5495
6500
  "application/json": components["schemas"]["ErrorEnvelope"];
5496
6501
  };
5497
6502
  };
5498
- /** @description Conflict concurrent or terminal state */
5499
- 409: {
6503
+ /** @description Not found */
6504
+ 404: {
5500
6505
  headers: {
5501
6506
  [name: string]: unknown;
5502
6507
  };
@@ -5530,35 +6535,35 @@ export interface paths {
5530
6535
  patch?: never;
5531
6536
  trace?: never;
5532
6537
  };
5533
- "/v1/trial/status": {
6538
+ "/v1/macos-sessions/rent": {
5534
6539
  parameters: {
5535
6540
  query?: never;
5536
6541
  header?: never;
5537
6542
  path?: never;
5538
6543
  cookie?: never;
5539
6544
  };
6545
+ get?: never;
6546
+ put?: never;
5540
6547
  /**
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.
6548
+ * Rent a hosted macOS session
6549
+ * @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`.
5543
6550
  */
5544
- get: {
6551
+ post: {
5545
6552
  parameters: {
5546
- query: {
5547
- token: string;
5548
- };
6553
+ query?: never;
5549
6554
  header?: never;
5550
6555
  path?: never;
5551
6556
  cookie?: never;
5552
6557
  };
5553
6558
  requestBody?: never;
5554
6559
  responses: {
5555
- /** @description Current trial status */
5556
- 200: {
6560
+ /** @description The rented session */
6561
+ 201: {
5557
6562
  headers: {
5558
6563
  [name: string]: unknown;
5559
6564
  };
5560
6565
  content: {
5561
- "application/json": components["schemas"]["TrialStatusEnvelope"];
6566
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5562
6567
  };
5563
6568
  };
5564
6569
  /** @description Bad request — validation failed */
@@ -5570,26 +6575,8 @@ export interface paths {
5570
6575
  "application/json": components["schemas"]["ErrorEnvelope"];
5571
6576
  };
5572
6577
  };
5573
- /** @description Not found */
5574
- 404: {
5575
- headers: {
5576
- [name: string]: unknown;
5577
- };
5578
- content: {
5579
- "application/json": components["schemas"]["ErrorEnvelope"];
5580
- };
5581
- };
5582
- /** @description Too many requests — rate limit exceeded */
5583
- 429: {
5584
- headers: {
5585
- [name: string]: unknown;
5586
- };
5587
- content: {
5588
- "application/json": components["schemas"]["ErrorEnvelope"];
5589
- };
5590
- };
5591
- /** @description Internal server error */
5592
- 500: {
6578
+ /** @description Unauthenticated missing or invalid bearer token */
6579
+ 401: {
5593
6580
  headers: {
5594
6581
  [name: string]: unknown;
5595
6582
  };
@@ -5597,47 +6584,8 @@ export interface paths {
5597
6584
  "application/json": components["schemas"]["ErrorEnvelope"];
5598
6585
  };
5599
6586
  };
5600
- };
5601
- };
5602
- put?: never;
5603
- post?: never;
5604
- delete?: never;
5605
- options?: never;
5606
- head?: never;
5607
- patch?: never;
5608
- trace?: never;
5609
- };
5610
- "/v1/trial/activity": {
5611
- parameters: {
5612
- query?: never;
5613
- header?: never;
5614
- path?: never;
5615
- cookie?: never;
5616
- };
5617
- /**
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).
5620
- */
5621
- get: {
5622
- parameters: {
5623
- query?: never;
5624
- header?: never;
5625
- path?: never;
5626
- cookie?: never;
5627
- };
5628
- requestBody?: never;
5629
- responses: {
5630
- /** @description Trial activity snapshot */
5631
- 200: {
5632
- headers: {
5633
- [name: string]: unknown;
5634
- };
5635
- content: {
5636
- "application/json": components["schemas"]["TrialActivityEnvelope"];
5637
- };
5638
- };
5639
- /** @description Bad request — validation failed */
5640
- 400: {
6587
+ /** @description Forbidden — caller authenticated but not allowed */
6588
+ 403: {
5641
6589
  headers: {
5642
6590
  [name: string]: unknown;
5643
6591
  };
@@ -5645,8 +6593,8 @@ export interface paths {
5645
6593
  "application/json": components["schemas"]["ErrorEnvelope"];
5646
6594
  };
5647
6595
  };
5648
- /** @description Unauthenticatedmissing or invalid bearer token */
5649
- 401: {
6596
+ /** @description Conflictconcurrent or terminal state */
6597
+ 409: {
5650
6598
  headers: {
5651
6599
  [name: string]: unknown;
5652
6600
  };
@@ -5674,15 +6622,13 @@ export interface paths {
5674
6622
  };
5675
6623
  };
5676
6624
  };
5677
- put?: never;
5678
- post?: never;
5679
6625
  delete?: never;
5680
6626
  options?: never;
5681
6627
  head?: never;
5682
6628
  patch?: never;
5683
6629
  trace?: never;
5684
6630
  };
5685
- "/v1/trial/{trial_id}/revoke": {
6631
+ "/v1/macos-sessions/{id}/cancel": {
5686
6632
  parameters: {
5687
6633
  query?: never;
5688
6634
  header?: never;
@@ -5692,32 +6638,27 @@ export interface paths {
5692
6638
  get?: never;
5693
6639
  put?: never;
5694
6640
  /**
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`.
6641
+ * Cancel a rented macOS session
6642
+ * @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
6643
  */
5698
6644
  post: {
5699
6645
  parameters: {
5700
6646
  query?: never;
5701
6647
  header?: never;
5702
6648
  path: {
5703
- /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
5704
- trial_id: string;
6649
+ id: string;
5705
6650
  };
5706
6651
  cookie?: never;
5707
6652
  };
5708
- requestBody?: {
5709
- content: {
5710
- "application/json": components["schemas"]["TrialRevokeBody"];
5711
- };
5712
- };
6653
+ requestBody?: never;
5713
6654
  responses: {
5714
- /** @description Trial revoked (idempotent) */
6655
+ /** @description The cancelled session */
5715
6656
  200: {
5716
6657
  headers: {
5717
6658
  [name: string]: unknown;
5718
6659
  };
5719
6660
  content: {
5720
- "application/json": components["schemas"]["TrialRevokeEnvelope"];
6661
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5721
6662
  };
5722
6663
  };
5723
6664
  /** @description Bad request — validation failed */
@@ -5738,15 +6679,6 @@ export interface paths {
5738
6679
  "application/json": components["schemas"]["ErrorEnvelope"];
5739
6680
  };
5740
6681
  };
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
6682
  /** @description Not found */
5751
6683
  404: {
5752
6684
  headers: {
@@ -5756,8 +6688,8 @@ export interface paths {
5756
6688
  "application/json": components["schemas"]["ErrorEnvelope"];
5757
6689
  };
5758
6690
  };
5759
- /** @description Unprocessabledomain rule violation */
5760
- 422: {
6691
+ /** @description Conflictconcurrent or terminal state */
6692
+ 409: {
5761
6693
  headers: {
5762
6694
  [name: string]: unknown;
5763
6695
  };
@@ -5791,7 +6723,7 @@ export interface paths {
5791
6723
  patch?: never;
5792
6724
  trace?: never;
5793
6725
  };
5794
- "/v1/byod/pair": {
6726
+ "/v1/macos-sessions/{id}/uncancel": {
5795
6727
  parameters: {
5796
6728
  query?: never;
5797
6729
  header?: never;
@@ -5801,29 +6733,27 @@ export interface paths {
5801
6733
  get?: never;
5802
6734
  put?: never;
5803
6735
  /**
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.
6736
+ * Keep a cancelled macOS session
6737
+ * @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
6738
  */
5807
6739
  post: {
5808
6740
  parameters: {
5809
6741
  query?: never;
5810
6742
  header?: never;
5811
- path?: never;
5812
- cookie?: never;
5813
- };
5814
- requestBody?: {
5815
- content: {
5816
- "application/json": components["schemas"]["ByodPairBody"];
6743
+ path: {
6744
+ id: string;
5817
6745
  };
6746
+ cookie?: never;
5818
6747
  };
6748
+ requestBody?: never;
5819
6749
  responses: {
5820
- /** @description Activation code + bridge download URL */
5821
- 201: {
6750
+ /** @description The session, no longer cancelled */
6751
+ 200: {
5822
6752
  headers: {
5823
6753
  [name: string]: unknown;
5824
6754
  };
5825
6755
  content: {
5826
- "application/json": components["schemas"]["ByodPairEnvelope"];
6756
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5827
6757
  };
5828
6758
  };
5829
6759
  /** @description Bad request — validation failed */
@@ -5853,6 +6783,15 @@ export interface paths {
5853
6783
  "application/json": components["schemas"]["ErrorEnvelope"];
5854
6784
  };
5855
6785
  };
6786
+ /** @description Conflict — concurrent or terminal state */
6787
+ 409: {
6788
+ headers: {
6789
+ [name: string]: unknown;
6790
+ };
6791
+ content: {
6792
+ "application/json": components["schemas"]["ErrorEnvelope"];
6793
+ };
6794
+ };
5856
6795
  /** @description Too many requests — rate limit exceeded */
5857
6796
  429: {
5858
6797
  headers: {
@@ -5888,7 +6827,7 @@ export interface paths {
5888
6827
  };
5889
6828
  /**
5890
6829
  * 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.
6830
+ * @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
6831
  */
5893
6832
  get: {
5894
6833
  parameters: {
@@ -5996,9 +6935,9 @@ export interface components {
5996
6935
  state_name: string | null;
5997
6936
  /** @enum {string} */
5998
6937
  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. */
6938
+ /** @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
6939
  byod: boolean;
6001
- /** @description True when the line is inbound-only: it may only message a handle that has messaged it first (a recipient who has never contacted the line is rejected with 403 outbound_first_forbidden). Inbound-only is a cheaper rental-tier variant (Bloo parity); always false on BYOD/pool lines. */
6940
+ /** @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
6941
  inbound_only: boolean;
6003
6942
  capabilities?: unknown;
6004
6943
  status: string;
@@ -6010,6 +6949,10 @@ export interface components {
6010
6949
  stripe_subscription_id: string | null;
6011
6950
  activated_at: string | null;
6012
6951
  expires_at: string | null;
6952
+ /** @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. */
6953
+ anniversary_at: string | null;
6954
+ /** @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. */
6955
+ cancel_at: string | null;
6013
6956
  created_at: string;
6014
6957
  metadata: {
6015
6958
  [key: string]: unknown;
@@ -6020,8 +6963,30 @@ export interface components {
6020
6963
  shared: boolean;
6021
6964
  fulfillment_status?: string | null;
6022
6965
  };
6023
- ApiKeyCreate: components["schemas"]["ApiKey"] & {
6024
- key: string;
6966
+ MacosSession: {
6967
+ /** @example mcs_7Kd2Nq8Rb4Xw1Ty6Ue9Ao3Ph5 */
6968
+ id: string;
6969
+ /** @description Whether this session is currently rented by the calling partner. `false` once it has been released back to iSnap stock. */
6970
+ rented: boolean;
6971
+ /**
6972
+ * Format: date-time
6973
+ * @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.
6974
+ * @example 2026-09-07T14:32:00.000Z
6975
+ */
6976
+ anniversary_at: string | null;
6977
+ /**
6978
+ * Format: date-time
6979
+ * @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.
6980
+ * @example 2026-10-07T14:32:00.000Z
6981
+ */
6982
+ cancel_at: string | null;
6983
+ /**
6984
+ * @description `pending_provisioning` while the Mac hosting the session has not been delivered yet; `created` once it has.
6985
+ * @enum {string}
6986
+ */
6987
+ status: "created" | "pending_provisioning";
6988
+ /** Format: date-time */
6989
+ created_at: string;
6025
6990
  };
6026
6991
  ApiKey: {
6027
6992
  /** Format: uuid */
@@ -6034,6 +6999,9 @@ export interface components {
6034
6999
  created_at: string;
6035
7000
  last_used_at: string | null;
6036
7001
  };
7002
+ ApiKeyCreate: components["schemas"]["ApiKey"] & {
7003
+ key: string;
7004
+ };
6037
7005
  ApiKeyList: {
6038
7006
  api_keys: components["schemas"]["ApiKey"][];
6039
7007
  };
@@ -6072,6 +7040,17 @@ export interface components {
6072
7040
  to_user_id: string;
6073
7041
  preserve_device_session?: boolean;
6074
7042
  };
7043
+ TerminateLine: {
7044
+ reason?: string;
7045
+ };
7046
+ SetLineAnniversary: {
7047
+ /**
7048
+ * Format: date-time
7049
+ * @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.
7050
+ * @example 2026-07-02T09:15:00Z
7051
+ */
7052
+ anniversary_at: string;
7053
+ };
6075
7054
  PatchLine: {
6076
7055
  metadata: {
6077
7056
  [key: string]: unknown;
@@ -6241,20 +7220,17 @@ export interface components {
6241
7220
  PreOrder: {
6242
7221
  /** Format: uuid */
6243
7222
  id: string;
6244
- /** @enum {string} */
6245
- billing_tier: "rental_iphone" | "rental_android";
6246
7223
  preferred_area_codes: string[];
7224
+ inbound_only: boolean;
6247
7225
  status: components["schemas"]["PreOrderStatus"];
6248
- stripe_payment_intent_id: string | null;
6249
7226
  stripe_client_secret: string | null;
6250
- amount: number;
6251
7227
  estimated_fulfillment: string | null;
6252
7228
  device_status?: components["schemas"]["DeviceFulfillmentStatus"];
6253
7229
  created_at: string;
6254
7230
  updated_at: string;
6255
7231
  };
6256
7232
  /** @enum {string} */
6257
- PreOrderStatus: "draft" | "pending" | "provisioning" | "ready" | "fulfilled" | "cancelled" | "refunded";
7233
+ PreOrderStatus: "draft" | "provisioning" | "fulfilled" | "cancelled";
6258
7234
  /** @enum {string|null} */
6259
7235
  DeviceFulfillmentStatus: "awaiting_hardware" | "received" | "setup_in_progress" | "ready" | "deployed" | null;
6260
7236
  PreOrderList: {
@@ -6298,11 +7274,71 @@ export interface components {
6298
7274
  */
6299
7275
  device_count: number;
6300
7276
  /**
6301
- * @description Stripe price cadence. Annual is 10x monthly (2 months free).
7277
+ * @description Stripe price cadence. Quarterly is 2.7x monthly (10% off) and annual is 10x monthly (2 months free).
7278
+ * @example monthly
7279
+ * @enum {string}
7280
+ */
7281
+ billing_cycle: "monthly" | "quarterly" | "annual";
7282
+ };
7283
+ MacCheckoutEnvelope: {
7284
+ /** @enum {boolean} */
7285
+ success: true;
7286
+ data: components["schemas"]["MacCheckoutResponse"];
7287
+ trace_id: string;
7288
+ request_id: string;
7289
+ };
7290
+ MacCheckoutResponse: {
7291
+ /**
7292
+ * Format: uri
7293
+ * @description Hosted Stripe Checkout URL — redirect the customer here.
7294
+ */
7295
+ checkout_url: string;
7296
+ /**
7297
+ * Format: date-time
7298
+ * @description Session expiry (ISO-8601). Stripe default is 24h.
7299
+ */
7300
+ expires_at: string;
7301
+ /** @description Sessions ordered — echoed back as accepted. */
7302
+ quantity: number;
7303
+ /** @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. */
7304
+ pack_size: number;
7305
+ /** @description Recurring total per period in integer cents, from the Price’s graduated tier ladder. Excludes tax, proration and coupons. */
7306
+ estimated_total_cents: number;
7307
+ };
7308
+ MacCheckoutBody: {
7309
+ /**
7310
+ * @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`.
7311
+ * @example 5
7312
+ */
7313
+ quantity: number;
7314
+ /**
7315
+ * @description Stripe price cadence for the session subscription.
6302
7316
  * @example monthly
6303
7317
  * @enum {string}
6304
7318
  */
6305
- billing_cycle: "monthly" | "annual";
7319
+ billing_cycle: "monthly" | "quarterly" | "annual";
7320
+ };
7321
+ SubscriptionQuantityEnvelope: {
7322
+ /** @enum {boolean} */
7323
+ success: true;
7324
+ data: components["schemas"]["SubscriptionQuantityResponse"];
7325
+ trace_id: string;
7326
+ request_id: string;
7327
+ };
7328
+ SubscriptionQuantityResponse: {
7329
+ /** @description The Stripe subscription ID (`sub_*`). */
7330
+ subscription_id: string;
7331
+ /** @description The quantity now set on the adjustable item. */
7332
+ quantity: number;
7333
+ /** @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. */
7334
+ estimated_total_cents: number | null;
7335
+ };
7336
+ SubscriptionQuantityBody: {
7337
+ /**
7338
+ * @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.
7339
+ * @example 10
7340
+ */
7341
+ quantity: number;
6306
7342
  };
6307
7343
  SubscriptionListEnvelope: {
6308
7344
  /** @enum {boolean} */
@@ -6515,7 +7551,7 @@ export interface components {
6515
7551
  * @description A concrete event type (e.g. `message.received`), a family wildcard (`line.*`), or the catch-all `*`.
6516
7552
  * @enum {string}
6517
7553
  */
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.*";
7554
+ 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" | "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
7555
  WebhookList: {
6520
7556
  webhooks: components["schemas"]["Webhook"][];
6521
7557
  };
@@ -6681,6 +7717,27 @@ export interface components {
6681
7717
  region: string;
6682
7718
  /** @example US */
6683
7719
  country_code: string;
7720
+ /**
7721
+ * @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.
7722
+ * @example 10019
7723
+ */
7724
+ purchase_zip: string | null;
7725
+ /**
7726
+ * @description Fallback ZIPs in the same geography, to retry with if the carrier rejects the primary. Empty when no ZIP is on record.
7727
+ * @example [
7728
+ * "10021",
7729
+ * "10023"
7730
+ * ]
7731
+ */
7732
+ alt_zips: string[];
7733
+ /**
7734
+ * @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.
7735
+ * @example [
7736
+ * "646",
7737
+ * "917"
7738
+ * ]
7739
+ */
7740
+ shared_with: string[];
6684
7741
  };
6685
7742
  Country: {
6686
7743
  /** @example US */
@@ -6907,6 +7964,13 @@ export interface components {
6907
7964
  [key: string]: unknown;
6908
7965
  };
6909
7966
  };
7967
+ MacosSessionEnvelope: {
7968
+ /** @enum {boolean} */
7969
+ success: true;
7970
+ data: components["schemas"]["MacosSession"];
7971
+ trace_id: string;
7972
+ request_id: string;
7973
+ };
6910
7974
  AppManifestEnvelope: {
6911
7975
  /** @enum {boolean} */
6912
7976
  success: true;
@@ -6926,10 +7990,16 @@ export interface components {
6926
7990
  */
6927
7991
  url: string;
6928
7992
  /**
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
7993
+ * @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.
7994
+ * @example 0.0.211
7995
+ */
7996
+ version: string | null;
7997
+ /**
7998
+ * @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.
7999
+ * @example published
8000
+ * @enum {string}
6931
8001
  */
6932
- version: string;
8002
+ version_status: "published" | "none_published" | "unreachable" | "misconfigured" | "not_configured";
6933
8003
  /**
6934
8004
  * @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
8005
  * @example true