@isnap/sdk 1.3.0-next.167 → 1.3.0-next.169

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.
@@ -1620,7 +1620,7 @@ export interface paths {
1620
1620
  patch?: never;
1621
1621
  trace?: never;
1622
1622
  };
1623
- "/v1/lines/{id}/release": {
1623
+ "/v1/lines/{id}/cancel": {
1624
1624
  parameters: {
1625
1625
  query?: never;
1626
1626
  header?: never;
@@ -1630,8 +1630,8 @@ export interface paths {
1630
1630
  get?: never;
1631
1631
  put?: never;
1632
1632
  /**
1633
- * Release a line back to the marketplace
1634
- * @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.
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`.
1635
1635
  */
1636
1636
  post: {
1637
1637
  parameters: {
@@ -1644,7 +1644,7 @@ export interface paths {
1644
1644
  };
1645
1645
  requestBody?: never;
1646
1646
  responses: {
1647
- /** @description Line released */
1647
+ /** @description Cancellation scheduled (or already scheduled — the call is idempotent). Returns the line, still `active`, with `cancel_at` set. */
1648
1648
  200: {
1649
1649
  headers: {
1650
1650
  [name: string]: unknown;
@@ -1695,6 +1695,15 @@ export interface paths {
1695
1695
  "application/json": components["schemas"]["ErrorEnvelope"];
1696
1696
  };
1697
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
+ };
1698
1707
  /** @description Too many requests — rate limit exceeded */
1699
1708
  429: {
1700
1709
  headers: {
@@ -1721,18 +1730,20 @@ export interface paths {
1721
1730
  patch?: never;
1722
1731
  trace?: never;
1723
1732
  };
1724
- "/v1/lines/{id}/quota": {
1733
+ "/v1/lines/{id}/uncancel": {
1725
1734
  parameters: {
1726
1735
  query?: never;
1727
1736
  header?: never;
1728
1737
  path?: never;
1729
1738
  cookie?: never;
1730
1739
  };
1740
+ get?: never;
1741
+ put?: never;
1731
1742
  /**
1732
- * Get a line's quota snapshot
1733
- * @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.
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.
1734
1745
  */
1735
- get: {
1746
+ post: {
1736
1747
  parameters: {
1737
1748
  query?: never;
1738
1749
  header?: never;
@@ -1743,13 +1754,19 @@ export interface paths {
1743
1754
  };
1744
1755
  requestBody?: never;
1745
1756
  responses: {
1746
- /** @description 5-bucket quota snapshot */
1757
+ /** @description Cancellation cleared, or the line was not cancelled. Returns the line with `cancel_at: null`. */
1747
1758
  200: {
1748
1759
  headers: {
1749
1760
  [name: string]: unknown;
1750
1761
  };
1751
1762
  content: {
1752
- "application/json": components["schemas"]["QuotaEnvelope"];
1763
+ "application/json": {
1764
+ /** @enum {boolean} */
1765
+ success: true;
1766
+ data: components["schemas"]["Line"];
1767
+ trace_id: string;
1768
+ request_id: string;
1769
+ };
1753
1770
  };
1754
1771
  };
1755
1772
  /** @description Bad request — validation failed */
@@ -1788,6 +1805,15 @@ export interface paths {
1788
1805
  "application/json": components["schemas"]["ErrorEnvelope"];
1789
1806
  };
1790
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
+ };
1791
1817
  /** @description Too many requests — rate limit exceeded */
1792
1818
  429: {
1793
1819
  headers: {
@@ -1808,47 +1834,53 @@ export interface paths {
1808
1834
  };
1809
1835
  };
1810
1836
  };
1811
- put?: never;
1812
- post?: never;
1813
1837
  delete?: never;
1814
1838
  options?: never;
1815
1839
  head?: never;
1816
1840
  patch?: never;
1817
1841
  trace?: never;
1818
1842
  };
1819
- "/v1/lines/{id}/queue": {
1843
+ "/v1/lines/{id}/anniversary": {
1820
1844
  parameters: {
1821
1845
  query?: never;
1822
1846
  header?: never;
1823
1847
  path?: never;
1824
1848
  cookie?: never;
1825
1849
  };
1850
+ get?: never;
1851
+ put?: never;
1826
1852
  /**
1827
- * Inspect a line's queued and scheduled messages
1828
- * @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.
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.
1829
1855
  */
1830
- get: {
1856
+ post: {
1831
1857
  parameters: {
1832
- query?: {
1833
- limit?: number;
1834
- cursor?: string;
1835
- status?: "queued" | "scheduled" | "delivered_to_device";
1836
- };
1858
+ query?: never;
1837
1859
  header?: never;
1838
1860
  path: {
1839
1861
  id: string;
1840
1862
  };
1841
1863
  cookie?: never;
1842
1864
  };
1843
- requestBody?: never;
1865
+ requestBody?: {
1866
+ content: {
1867
+ "application/json": components["schemas"]["SetLineAnniversary"];
1868
+ };
1869
+ };
1844
1870
  responses: {
1845
- /** @description Queue inspection — summary + paginated items */
1871
+ /** @description Anniversary recorded. Returns the line with the new `anniversary_at`. */
1846
1872
  200: {
1847
1873
  headers: {
1848
1874
  [name: string]: unknown;
1849
1875
  };
1850
1876
  content: {
1851
- "application/json": components["schemas"]["QueueEnvelope"];
1877
+ "application/json": {
1878
+ /** @enum {boolean} */
1879
+ success: true;
1880
+ data: components["schemas"]["Line"];
1881
+ trace_id: string;
1882
+ request_id: string;
1883
+ };
1852
1884
  };
1853
1885
  };
1854
1886
  /** @description Bad request — validation failed */
@@ -1887,6 +1919,15 @@ export interface paths {
1887
1919
  "application/json": components["schemas"]["ErrorEnvelope"];
1888
1920
  };
1889
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
+ };
1890
1931
  /** @description Too many requests — rate limit exceeded */
1891
1932
  429: {
1892
1933
  headers: {
@@ -1907,26 +1948,26 @@ export interface paths {
1907
1948
  };
1908
1949
  };
1909
1950
  };
1910
- put?: never;
1911
- post?: never;
1912
1951
  delete?: never;
1913
1952
  options?: never;
1914
1953
  head?: never;
1915
1954
  patch?: never;
1916
1955
  trace?: never;
1917
1956
  };
1918
- "/v1/lines/{id}/health": {
1957
+ "/v1/lines/{id}/release": {
1919
1958
  parameters: {
1920
1959
  query?: never;
1921
1960
  header?: never;
1922
1961
  path?: never;
1923
1962
  cookie?: never;
1924
1963
  };
1964
+ get?: never;
1965
+ put?: never;
1925
1966
  /**
1926
- * Get a BYOD line's health snapshot
1927
- * @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.
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.
1928
1969
  */
1929
- get: {
1970
+ post: {
1930
1971
  parameters: {
1931
1972
  query?: never;
1932
1973
  header?: never;
@@ -1937,13 +1978,19 @@ export interface paths {
1937
1978
  };
1938
1979
  requestBody?: never;
1939
1980
  responses: {
1940
- /** @description BYOD line health snapshot */
1981
+ /** @description Line released */
1941
1982
  200: {
1942
1983
  headers: {
1943
1984
  [name: string]: unknown;
1944
1985
  };
1945
1986
  content: {
1946
- "application/json": components["schemas"]["LineHealthEnvelope"];
1987
+ "application/json": {
1988
+ /** @enum {boolean} */
1989
+ success: true;
1990
+ data: components["schemas"]["Line"];
1991
+ trace_id: string;
1992
+ request_id: string;
1993
+ };
1947
1994
  };
1948
1995
  };
1949
1996
  /** @description Bad request — validation failed */
@@ -2002,15 +2049,13 @@ export interface paths {
2002
2049
  };
2003
2050
  };
2004
2051
  };
2005
- put?: never;
2006
- post?: never;
2007
2052
  delete?: never;
2008
2053
  options?: never;
2009
2054
  head?: never;
2010
2055
  patch?: never;
2011
2056
  trace?: never;
2012
2057
  };
2013
- "/v1/lines/{id}/config": {
2058
+ "/v1/lines/{id}/quota": {
2014
2059
  parameters: {
2015
2060
  query?: never;
2016
2061
  header?: never;
@@ -2018,8 +2063,8 @@ export interface paths {
2018
2063
  cookie?: never;
2019
2064
  };
2020
2065
  /**
2021
- * Get a line's effective send configuration
2022
- * @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.
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.
2023
2068
  */
2024
2069
  get: {
2025
2070
  parameters: {
@@ -2032,13 +2077,13 @@ export interface paths {
2032
2077
  };
2033
2078
  requestBody?: never;
2034
2079
  responses: {
2035
- /** @description Effective config (tier defaults + per-line overrides) */
2080
+ /** @description 5-bucket quota snapshot */
2036
2081
  200: {
2037
2082
  headers: {
2038
2083
  [name: string]: unknown;
2039
2084
  };
2040
2085
  content: {
2041
- "application/json": components["schemas"]["LineConfigEnvelope"];
2086
+ "application/json": components["schemas"]["QuotaEnvelope"];
2042
2087
  };
2043
2088
  };
2044
2089
  /** @description Bad request — validation failed */
@@ -2102,32 +2147,42 @@ export interface paths {
2102
2147
  delete?: never;
2103
2148
  options?: never;
2104
2149
  head?: never;
2150
+ patch?: never;
2151
+ trace?: never;
2152
+ };
2153
+ "/v1/lines/{id}/queue": {
2154
+ parameters: {
2155
+ query?: never;
2156
+ header?: never;
2157
+ path?: never;
2158
+ cookie?: never;
2159
+ };
2105
2160
  /**
2106
- * Update a line's send configuration
2107
- * @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.
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.
2108
2163
  */
2109
- patch: {
2164
+ get: {
2110
2165
  parameters: {
2111
- query?: never;
2166
+ query?: {
2167
+ limit?: number;
2168
+ cursor?: string;
2169
+ status?: "queued" | "scheduled" | "delivered_to_device";
2170
+ };
2112
2171
  header?: never;
2113
2172
  path: {
2114
2173
  id: string;
2115
2174
  };
2116
2175
  cookie?: never;
2117
2176
  };
2118
- requestBody?: {
2119
- content: {
2120
- "application/json": components["schemas"]["LineConfigPatch"];
2121
- };
2122
- };
2177
+ requestBody?: never;
2123
2178
  responses: {
2124
- /** @description Updated effective config (merged) + overridden_keys */
2179
+ /** @description Queue inspection — summary + paginated items */
2125
2180
  200: {
2126
2181
  headers: {
2127
2182
  [name: string]: unknown;
2128
2183
  };
2129
2184
  content: {
2130
- "application/json": components["schemas"]["LineConfigEnvelope"];
2185
+ "application/json": components["schemas"]["QueueEnvelope"];
2131
2186
  };
2132
2187
  };
2133
2188
  /** @description Bad request — validation failed */
@@ -2186,9 +2241,15 @@ export interface paths {
2186
2241
  };
2187
2242
  };
2188
2243
  };
2244
+ put?: never;
2245
+ post?: never;
2246
+ delete?: never;
2247
+ options?: never;
2248
+ head?: never;
2249
+ patch?: never;
2189
2250
  trace?: never;
2190
2251
  };
2191
- "/v1/pre-orders": {
2252
+ "/v1/lines/{id}/health": {
2192
2253
  parameters: {
2193
2254
  query?: never;
2194
2255
  header?: never;
@@ -2196,29 +2257,27 @@ export interface paths {
2196
2257
  cookie?: never;
2197
2258
  };
2198
2259
  /**
2199
- * List pre-orders
2200
- * @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.
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.
2201
2262
  */
2202
2263
  get: {
2203
2264
  parameters: {
2204
- query?: {
2205
- status?: "draft" | "provisioning" | "fulfilled" | "cancelled";
2206
- cursor?: string;
2207
- limit?: number;
2208
- };
2265
+ query?: never;
2209
2266
  header?: never;
2210
- path?: never;
2267
+ path: {
2268
+ id: string;
2269
+ };
2211
2270
  cookie?: never;
2212
2271
  };
2213
2272
  requestBody?: never;
2214
2273
  responses: {
2215
- /** @description Pre-order list */
2274
+ /** @description BYOD line health snapshot */
2216
2275
  200: {
2217
2276
  headers: {
2218
2277
  [name: string]: unknown;
2219
2278
  };
2220
2279
  content: {
2221
- "application/json": components["schemas"]["PreOrderList"];
2280
+ "application/json": components["schemas"]["LineHealthEnvelope"];
2222
2281
  };
2223
2282
  };
2224
2283
  /** @description Bad request — validation failed */
@@ -2239,6 +2298,24 @@ export interface paths {
2239
2298
  "application/json": components["schemas"]["ErrorEnvelope"];
2240
2299
  };
2241
2300
  };
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
+ };
2242
2319
  /** @description Too many requests — rate limit exceeded */
2243
2320
  429: {
2244
2321
  headers: {
@@ -2260,43 +2337,42 @@ export interface paths {
2260
2337
  };
2261
2338
  };
2262
2339
  put?: never;
2340
+ post?: never;
2341
+ delete?: never;
2342
+ options?: never;
2343
+ head?: never;
2344
+ patch?: never;
2345
+ trace?: never;
2346
+ };
2347
+ "/v1/lines/{id}/config": {
2348
+ parameters: {
2349
+ query?: never;
2350
+ header?: never;
2351
+ path?: never;
2352
+ cookie?: never;
2353
+ };
2263
2354
  /**
2264
- * Create a pre-order (draft)
2265
- * @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.
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.
2266
2357
  */
2267
- post: {
2358
+ get: {
2268
2359
  parameters: {
2269
2360
  query?: never;
2270
2361
  header?: never;
2271
- path?: never;
2272
- cookie?: never;
2273
- };
2274
- requestBody?: {
2275
- content: {
2276
- "application/json": {
2277
- /** @default [] */
2278
- preferred_area_codes?: string[];
2279
- /** @default false */
2280
- inbound_only?: boolean;
2281
- /** @enum {string} */
2282
- billing_tier?: "rental_iphone" | "rental_android" | "byod_imessage" | "byod_android" | "shared_pool" | "shared_imessage";
2283
- };
2362
+ path: {
2363
+ id: string;
2284
2364
  };
2365
+ cookie?: never;
2285
2366
  };
2367
+ requestBody?: never;
2286
2368
  responses: {
2287
- /** @description Pre-order draft created */
2288
- 201: {
2369
+ /** @description Effective config (tier defaults + per-line overrides) */
2370
+ 200: {
2289
2371
  headers: {
2290
2372
  [name: string]: unknown;
2291
2373
  };
2292
2374
  content: {
2293
- "application/json": {
2294
- /** @enum {boolean} */
2295
- success: true;
2296
- data: components["schemas"]["PreOrder"];
2297
- trace_id: string;
2298
- request_id: string;
2299
- };
2375
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2300
2376
  };
2301
2377
  };
2302
2378
  /** @description Bad request — validation failed */
@@ -2317,6 +2393,24 @@ export interface paths {
2317
2393
  "application/json": components["schemas"]["ErrorEnvelope"];
2318
2394
  };
2319
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
+ };
2405
+ /** @description Not found */
2406
+ 404: {
2407
+ headers: {
2408
+ [name: string]: unknown;
2409
+ };
2410
+ content: {
2411
+ "application/json": components["schemas"]["ErrorEnvelope"];
2412
+ };
2413
+ };
2320
2414
  /** @description Too many requests — rate limit exceeded */
2321
2415
  429: {
2322
2416
  headers: {
@@ -2337,10 +2431,250 @@ export interface paths {
2337
2431
  };
2338
2432
  };
2339
2433
  };
2434
+ put?: never;
2435
+ post?: never;
2340
2436
  delete?: never;
2341
2437
  options?: never;
2342
2438
  head?: never;
2343
- patch?: never;
2439
+ /**
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.
2442
+ */
2443
+ patch: {
2444
+ parameters: {
2445
+ query?: never;
2446
+ header?: never;
2447
+ path: {
2448
+ id: string;
2449
+ };
2450
+ cookie?: never;
2451
+ };
2452
+ requestBody?: {
2453
+ content: {
2454
+ "application/json": components["schemas"]["LineConfigPatch"];
2455
+ };
2456
+ };
2457
+ responses: {
2458
+ /** @description Updated effective config (merged) + overridden_keys */
2459
+ 200: {
2460
+ headers: {
2461
+ [name: string]: unknown;
2462
+ };
2463
+ content: {
2464
+ "application/json": components["schemas"]["LineConfigEnvelope"];
2465
+ };
2466
+ };
2467
+ /** @description Bad request — validation failed */
2468
+ 400: {
2469
+ headers: {
2470
+ [name: string]: unknown;
2471
+ };
2472
+ content: {
2473
+ "application/json": components["schemas"]["ErrorEnvelope"];
2474
+ };
2475
+ };
2476
+ /** @description Unauthenticated — missing or invalid bearer token */
2477
+ 401: {
2478
+ headers: {
2479
+ [name: string]: unknown;
2480
+ };
2481
+ content: {
2482
+ "application/json": components["schemas"]["ErrorEnvelope"];
2483
+ };
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
+ };
2494
+ /** @description Not found */
2495
+ 404: {
2496
+ headers: {
2497
+ [name: string]: unknown;
2498
+ };
2499
+ content: {
2500
+ "application/json": components["schemas"]["ErrorEnvelope"];
2501
+ };
2502
+ };
2503
+ /** @description Too many requests — rate limit exceeded */
2504
+ 429: {
2505
+ headers: {
2506
+ [name: string]: unknown;
2507
+ };
2508
+ content: {
2509
+ "application/json": components["schemas"]["ErrorEnvelope"];
2510
+ };
2511
+ };
2512
+ /** @description Internal server error */
2513
+ 500: {
2514
+ headers: {
2515
+ [name: string]: unknown;
2516
+ };
2517
+ content: {
2518
+ "application/json": components["schemas"]["ErrorEnvelope"];
2519
+ };
2520
+ };
2521
+ };
2522
+ };
2523
+ trace?: never;
2524
+ };
2525
+ "/v1/pre-orders": {
2526
+ parameters: {
2527
+ query?: never;
2528
+ header?: never;
2529
+ path?: never;
2530
+ cookie?: never;
2531
+ };
2532
+ /**
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.
2535
+ */
2536
+ get: {
2537
+ parameters: {
2538
+ query?: {
2539
+ status?: "draft" | "provisioning" | "fulfilled" | "cancelled";
2540
+ cursor?: string;
2541
+ limit?: number;
2542
+ };
2543
+ header?: never;
2544
+ path?: never;
2545
+ cookie?: never;
2546
+ };
2547
+ requestBody?: never;
2548
+ responses: {
2549
+ /** @description Pre-order list */
2550
+ 200: {
2551
+ headers: {
2552
+ [name: string]: unknown;
2553
+ };
2554
+ content: {
2555
+ "application/json": components["schemas"]["PreOrderList"];
2556
+ };
2557
+ };
2558
+ /** @description Bad request — validation failed */
2559
+ 400: {
2560
+ headers: {
2561
+ [name: string]: unknown;
2562
+ };
2563
+ content: {
2564
+ "application/json": components["schemas"]["ErrorEnvelope"];
2565
+ };
2566
+ };
2567
+ /** @description Unauthenticated — missing or invalid bearer token */
2568
+ 401: {
2569
+ headers: {
2570
+ [name: string]: unknown;
2571
+ };
2572
+ content: {
2573
+ "application/json": components["schemas"]["ErrorEnvelope"];
2574
+ };
2575
+ };
2576
+ /** @description Too many requests — rate limit exceeded */
2577
+ 429: {
2578
+ headers: {
2579
+ [name: string]: unknown;
2580
+ };
2581
+ content: {
2582
+ "application/json": components["schemas"]["ErrorEnvelope"];
2583
+ };
2584
+ };
2585
+ /** @description Internal server error */
2586
+ 500: {
2587
+ headers: {
2588
+ [name: string]: unknown;
2589
+ };
2590
+ content: {
2591
+ "application/json": components["schemas"]["ErrorEnvelope"];
2592
+ };
2593
+ };
2594
+ };
2595
+ };
2596
+ put?: never;
2597
+ /**
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.
2600
+ */
2601
+ post: {
2602
+ parameters: {
2603
+ query?: never;
2604
+ header?: never;
2605
+ path?: never;
2606
+ cookie?: never;
2607
+ };
2608
+ requestBody?: {
2609
+ content: {
2610
+ "application/json": {
2611
+ /** @default [] */
2612
+ preferred_area_codes?: string[];
2613
+ /** @default false */
2614
+ inbound_only?: boolean;
2615
+ /** @enum {string} */
2616
+ billing_tier?: "rental_iphone" | "rental_android" | "byod_imessage" | "byod_android" | "shared_pool" | "shared_imessage";
2617
+ };
2618
+ };
2619
+ };
2620
+ responses: {
2621
+ /** @description Pre-order draft created */
2622
+ 201: {
2623
+ headers: {
2624
+ [name: string]: unknown;
2625
+ };
2626
+ content: {
2627
+ "application/json": {
2628
+ /** @enum {boolean} */
2629
+ success: true;
2630
+ data: components["schemas"]["PreOrder"];
2631
+ trace_id: string;
2632
+ request_id: string;
2633
+ };
2634
+ };
2635
+ };
2636
+ /** @description Bad request — validation failed */
2637
+ 400: {
2638
+ headers: {
2639
+ [name: string]: unknown;
2640
+ };
2641
+ content: {
2642
+ "application/json": components["schemas"]["ErrorEnvelope"];
2643
+ };
2644
+ };
2645
+ /** @description Unauthenticated — missing or invalid bearer token */
2646
+ 401: {
2647
+ headers: {
2648
+ [name: string]: unknown;
2649
+ };
2650
+ content: {
2651
+ "application/json": components["schemas"]["ErrorEnvelope"];
2652
+ };
2653
+ };
2654
+ /** @description Too many requests — rate limit exceeded */
2655
+ 429: {
2656
+ headers: {
2657
+ [name: string]: unknown;
2658
+ };
2659
+ content: {
2660
+ "application/json": components["schemas"]["ErrorEnvelope"];
2661
+ };
2662
+ };
2663
+ /** @description Internal server error */
2664
+ 500: {
2665
+ headers: {
2666
+ [name: string]: unknown;
2667
+ };
2668
+ content: {
2669
+ "application/json": components["schemas"]["ErrorEnvelope"];
2670
+ };
2671
+ };
2672
+ };
2673
+ };
2674
+ delete?: never;
2675
+ options?: never;
2676
+ head?: never;
2677
+ patch?: never;
2344
2678
  trace?: never;
2345
2679
  };
2346
2680
  "/v1/pre-orders/{id}/confirm": {
@@ -5559,8 +5893,341 @@ export interface paths {
5559
5893
  "application/json": components["schemas"]["ErrorEnvelope"];
5560
5894
  };
5561
5895
  };
5562
- /** @description Unprocessable — domain rule violation */
5563
- 422: {
5896
+ /** @description Unprocessable — domain rule violation */
5897
+ 422: {
5898
+ headers: {
5899
+ [name: string]: unknown;
5900
+ };
5901
+ content: {
5902
+ "application/json": components["schemas"]["ErrorEnvelope"];
5903
+ };
5904
+ };
5905
+ /** @description Too many requests — rate limit exceeded */
5906
+ 429: {
5907
+ headers: {
5908
+ [name: string]: unknown;
5909
+ };
5910
+ content: {
5911
+ "application/json": components["schemas"]["ErrorEnvelope"];
5912
+ };
5913
+ };
5914
+ /** @description Internal server error */
5915
+ 500: {
5916
+ headers: {
5917
+ [name: string]: unknown;
5918
+ };
5919
+ content: {
5920
+ "application/json": components["schemas"]["ErrorEnvelope"];
5921
+ };
5922
+ };
5923
+ };
5924
+ };
5925
+ delete: {
5926
+ parameters: {
5927
+ query?: never;
5928
+ header?: never;
5929
+ path: {
5930
+ chat_id: string;
5931
+ };
5932
+ cookie?: never;
5933
+ };
5934
+ requestBody?: never;
5935
+ responses: {
5936
+ /** @description Typing indicator stopped */
5937
+ 200: {
5938
+ headers: {
5939
+ [name: string]: unknown;
5940
+ };
5941
+ content: {
5942
+ "application/json": components["schemas"]["ChatTypingStopEnvelope"];
5943
+ };
5944
+ };
5945
+ /** @description Bad request — validation failed */
5946
+ 400: {
5947
+ headers: {
5948
+ [name: string]: unknown;
5949
+ };
5950
+ content: {
5951
+ "application/json": components["schemas"]["ErrorEnvelope"];
5952
+ };
5953
+ };
5954
+ /** @description Unauthenticated — missing or invalid bearer token */
5955
+ 401: {
5956
+ headers: {
5957
+ [name: string]: unknown;
5958
+ };
5959
+ content: {
5960
+ "application/json": components["schemas"]["ErrorEnvelope"];
5961
+ };
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
+ };
5972
+ /** @description Not found */
5973
+ 404: {
5974
+ headers: {
5975
+ [name: string]: unknown;
5976
+ };
5977
+ content: {
5978
+ "application/json": components["schemas"]["ErrorEnvelope"];
5979
+ };
5980
+ };
5981
+ /** @description Too many requests — rate limit exceeded */
5982
+ 429: {
5983
+ headers: {
5984
+ [name: string]: unknown;
5985
+ };
5986
+ content: {
5987
+ "application/json": components["schemas"]["ErrorEnvelope"];
5988
+ };
5989
+ };
5990
+ /** @description Internal server error */
5991
+ 500: {
5992
+ headers: {
5993
+ [name: string]: unknown;
5994
+ };
5995
+ content: {
5996
+ "application/json": components["schemas"]["ErrorEnvelope"];
5997
+ };
5998
+ };
5999
+ };
6000
+ };
6001
+ options?: never;
6002
+ head?: never;
6003
+ patch?: never;
6004
+ trace?: never;
6005
+ };
6006
+ "/v1/chats/{chat_id}/read": {
6007
+ parameters: {
6008
+ query?: never;
6009
+ header?: never;
6010
+ path?: never;
6011
+ cookie?: never;
6012
+ };
6013
+ get?: never;
6014
+ put?: never;
6015
+ post: {
6016
+ parameters: {
6017
+ query?: never;
6018
+ header?: never;
6019
+ path: {
6020
+ chat_id: string;
6021
+ };
6022
+ cookie?: never;
6023
+ };
6024
+ requestBody?: {
6025
+ content: {
6026
+ "application/json": Record<string, never>;
6027
+ };
6028
+ };
6029
+ responses: {
6030
+ /** @description Chat marked read */
6031
+ 200: {
6032
+ headers: {
6033
+ [name: string]: unknown;
6034
+ };
6035
+ content: {
6036
+ "application/json": components["schemas"]["ChatMarkReadEnvelope"];
6037
+ };
6038
+ };
6039
+ /** @description Bad request — validation failed */
6040
+ 400: {
6041
+ headers: {
6042
+ [name: string]: unknown;
6043
+ };
6044
+ content: {
6045
+ "application/json": components["schemas"]["ErrorEnvelope"];
6046
+ };
6047
+ };
6048
+ /** @description Unauthenticated — missing or invalid bearer token */
6049
+ 401: {
6050
+ headers: {
6051
+ [name: string]: unknown;
6052
+ };
6053
+ content: {
6054
+ "application/json": components["schemas"]["ErrorEnvelope"];
6055
+ };
6056
+ };
6057
+ /** @description Forbidden — caller authenticated but not allowed */
6058
+ 403: {
6059
+ headers: {
6060
+ [name: string]: unknown;
6061
+ };
6062
+ content: {
6063
+ "application/json": components["schemas"]["ErrorEnvelope"];
6064
+ };
6065
+ };
6066
+ /** @description Not found */
6067
+ 404: {
6068
+ headers: {
6069
+ [name: string]: unknown;
6070
+ };
6071
+ content: {
6072
+ "application/json": components["schemas"]["ErrorEnvelope"];
6073
+ };
6074
+ };
6075
+ /** @description Too many requests — rate limit exceeded */
6076
+ 429: {
6077
+ headers: {
6078
+ [name: string]: unknown;
6079
+ };
6080
+ content: {
6081
+ "application/json": components["schemas"]["ErrorEnvelope"];
6082
+ };
6083
+ };
6084
+ /** @description Internal server error */
6085
+ 500: {
6086
+ headers: {
6087
+ [name: string]: unknown;
6088
+ };
6089
+ content: {
6090
+ "application/json": components["schemas"]["ErrorEnvelope"];
6091
+ };
6092
+ };
6093
+ };
6094
+ };
6095
+ delete?: never;
6096
+ options?: never;
6097
+ head?: never;
6098
+ patch?: never;
6099
+ trace?: never;
6100
+ };
6101
+ "/v1/trial/init": {
6102
+ parameters: {
6103
+ query?: never;
6104
+ header?: never;
6105
+ path?: never;
6106
+ cookie?: never;
6107
+ };
6108
+ get?: never;
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
+ */
6114
+ post: {
6115
+ parameters: {
6116
+ query?: never;
6117
+ header?: never;
6118
+ path?: never;
6119
+ cookie?: never;
6120
+ };
6121
+ requestBody?: {
6122
+ content: {
6123
+ "application/json": components["schemas"]["TrialInitBody"];
6124
+ };
6125
+ };
6126
+ responses: {
6127
+ /** @description Trial session created — pairing pending */
6128
+ 201: {
6129
+ headers: {
6130
+ [name: string]: unknown;
6131
+ };
6132
+ content: {
6133
+ "application/json": components["schemas"]["TrialInitEnvelope"];
6134
+ };
6135
+ };
6136
+ /** @description Bad request — validation failed */
6137
+ 400: {
6138
+ headers: {
6139
+ [name: string]: unknown;
6140
+ };
6141
+ content: {
6142
+ "application/json": components["schemas"]["ErrorEnvelope"];
6143
+ };
6144
+ };
6145
+ /** @description Unauthenticated — missing or invalid bearer token */
6146
+ 401: {
6147
+ headers: {
6148
+ [name: string]: unknown;
6149
+ };
6150
+ content: {
6151
+ "application/json": components["schemas"]["ErrorEnvelope"];
6152
+ };
6153
+ };
6154
+ /** @description Conflict — concurrent or terminal state */
6155
+ 409: {
6156
+ headers: {
6157
+ [name: string]: unknown;
6158
+ };
6159
+ content: {
6160
+ "application/json": components["schemas"]["ErrorEnvelope"];
6161
+ };
6162
+ };
6163
+ /** @description Too many requests — rate limit exceeded */
6164
+ 429: {
6165
+ headers: {
6166
+ [name: string]: unknown;
6167
+ };
6168
+ content: {
6169
+ "application/json": components["schemas"]["ErrorEnvelope"];
6170
+ };
6171
+ };
6172
+ /** @description Internal server error */
6173
+ 500: {
6174
+ headers: {
6175
+ [name: string]: unknown;
6176
+ };
6177
+ content: {
6178
+ "application/json": components["schemas"]["ErrorEnvelope"];
6179
+ };
6180
+ };
6181
+ };
6182
+ };
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: {
6201
+ parameters: {
6202
+ query: {
6203
+ token: string;
6204
+ };
6205
+ header?: never;
6206
+ path?: never;
6207
+ cookie?: never;
6208
+ };
6209
+ requestBody?: never;
6210
+ responses: {
6211
+ /** @description Current trial status */
6212
+ 200: {
6213
+ headers: {
6214
+ [name: string]: unknown;
6215
+ };
6216
+ content: {
6217
+ "application/json": components["schemas"]["TrialStatusEnvelope"];
6218
+ };
6219
+ };
6220
+ /** @description Bad request — validation failed */
6221
+ 400: {
6222
+ headers: {
6223
+ [name: string]: unknown;
6224
+ };
6225
+ content: {
6226
+ "application/json": components["schemas"]["ErrorEnvelope"];
6227
+ };
6228
+ };
6229
+ /** @description Not found */
6230
+ 404: {
5564
6231
  headers: {
5565
6232
  [name: string]: unknown;
5566
6233
  };
@@ -5588,24 +6255,41 @@ export interface paths {
5588
6255
  };
5589
6256
  };
5590
6257
  };
5591
- delete: {
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: {
5592
6278
  parameters: {
5593
6279
  query?: never;
5594
6280
  header?: never;
5595
- path: {
5596
- chat_id: string;
5597
- };
6281
+ path?: never;
5598
6282
  cookie?: never;
5599
6283
  };
5600
6284
  requestBody?: never;
5601
6285
  responses: {
5602
- /** @description Typing indicator stopped */
6286
+ /** @description Trial activity snapshot */
5603
6287
  200: {
5604
6288
  headers: {
5605
6289
  [name: string]: unknown;
5606
6290
  };
5607
6291
  content: {
5608
- "application/json": components["schemas"]["ChatTypingStopEnvelope"];
6292
+ "application/json": components["schemas"]["TrialActivityEnvelope"];
5609
6293
  };
5610
6294
  };
5611
6295
  /** @description Bad request — validation failed */
@@ -5626,24 +6310,6 @@ export interface paths {
5626
6310
  "application/json": components["schemas"]["ErrorEnvelope"];
5627
6311
  };
5628
6312
  };
5629
- /** @description Forbidden — caller authenticated but not allowed */
5630
- 403: {
5631
- headers: {
5632
- [name: string]: unknown;
5633
- };
5634
- content: {
5635
- "application/json": components["schemas"]["ErrorEnvelope"];
5636
- };
5637
- };
5638
- /** @description Not found */
5639
- 404: {
5640
- headers: {
5641
- [name: string]: unknown;
5642
- };
5643
- content: {
5644
- "application/json": components["schemas"]["ErrorEnvelope"];
5645
- };
5646
- };
5647
6313
  /** @description Too many requests — rate limit exceeded */
5648
6314
  429: {
5649
6315
  headers: {
@@ -5664,12 +6330,15 @@ export interface paths {
5664
6330
  };
5665
6331
  };
5666
6332
  };
6333
+ put?: never;
6334
+ post?: never;
6335
+ delete?: never;
5667
6336
  options?: never;
5668
6337
  head?: never;
5669
6338
  patch?: never;
5670
6339
  trace?: never;
5671
6340
  };
5672
- "/v1/chats/{chat_id}/read": {
6341
+ "/v1/trial/{trial_id}/revoke": {
5673
6342
  parameters: {
5674
6343
  query?: never;
5675
6344
  header?: never;
@@ -5678,28 +6347,33 @@ export interface paths {
5678
6347
  };
5679
6348
  get?: never;
5680
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
+ */
5681
6354
  post: {
5682
6355
  parameters: {
5683
6356
  query?: never;
5684
6357
  header?: never;
5685
6358
  path: {
5686
- chat_id: string;
6359
+ /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
6360
+ trial_id: string;
5687
6361
  };
5688
6362
  cookie?: never;
5689
6363
  };
5690
6364
  requestBody?: {
5691
6365
  content: {
5692
- "application/json": Record<string, never>;
6366
+ "application/json": components["schemas"]["TrialRevokeBody"];
5693
6367
  };
5694
6368
  };
5695
6369
  responses: {
5696
- /** @description Chat marked read */
6370
+ /** @description Trial revoked (idempotent) */
5697
6371
  200: {
5698
6372
  headers: {
5699
6373
  [name: string]: unknown;
5700
6374
  };
5701
6375
  content: {
5702
- "application/json": components["schemas"]["ChatMarkReadEnvelope"];
6376
+ "application/json": components["schemas"]["TrialRevokeEnvelope"];
5703
6377
  };
5704
6378
  };
5705
6379
  /** @description Bad request — validation failed */
@@ -5738,6 +6412,15 @@ export interface paths {
5738
6412
  "application/json": components["schemas"]["ErrorEnvelope"];
5739
6413
  };
5740
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
+ };
5741
6424
  /** @description Too many requests — rate limit exceeded */
5742
6425
  429: {
5743
6426
  headers: {
@@ -5764,7 +6447,7 @@ export interface paths {
5764
6447
  patch?: never;
5765
6448
  trace?: never;
5766
6449
  };
5767
- "/v1/trial/init": {
6450
+ "/v1/byod/pair": {
5768
6451
  parameters: {
5769
6452
  query?: never;
5770
6453
  header?: never;
@@ -5774,8 +6457,8 @@ export interface paths {
5774
6457
  get?: never;
5775
6458
  put?: never;
5776
6459
  /**
5777
- * Start a trial session
5778
- * @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.
5779
6462
  */
5780
6463
  post: {
5781
6464
  parameters: {
@@ -5786,17 +6469,17 @@ export interface paths {
5786
6469
  };
5787
6470
  requestBody?: {
5788
6471
  content: {
5789
- "application/json": components["schemas"]["TrialInitBody"];
6472
+ "application/json": components["schemas"]["ByodPairBody"];
5790
6473
  };
5791
6474
  };
5792
6475
  responses: {
5793
- /** @description Trial session created — pairing pending */
6476
+ /** @description Activation code + bridge download URL */
5794
6477
  201: {
5795
6478
  headers: {
5796
6479
  [name: string]: unknown;
5797
6480
  };
5798
6481
  content: {
5799
- "application/json": components["schemas"]["TrialInitEnvelope"];
6482
+ "application/json": components["schemas"]["ByodPairEnvelope"];
5800
6483
  };
5801
6484
  };
5802
6485
  /** @description Bad request — validation failed */
@@ -5817,8 +6500,8 @@ export interface paths {
5817
6500
  "application/json": components["schemas"]["ErrorEnvelope"];
5818
6501
  };
5819
6502
  };
5820
- /** @description Conflict — concurrent or terminal state */
5821
- 409: {
6503
+ /** @description Not found */
6504
+ 404: {
5822
6505
  headers: {
5823
6506
  [name: string]: unknown;
5824
6507
  };
@@ -5852,35 +6535,35 @@ export interface paths {
5852
6535
  patch?: never;
5853
6536
  trace?: never;
5854
6537
  };
5855
- "/v1/trial/status": {
6538
+ "/v1/macos-sessions/rent": {
5856
6539
  parameters: {
5857
6540
  query?: never;
5858
6541
  header?: never;
5859
6542
  path?: never;
5860
6543
  cookie?: never;
5861
6544
  };
6545
+ get?: never;
6546
+ put?: never;
5862
6547
  /**
5863
- * Check trial pairing status
5864
- * @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`.
5865
6550
  */
5866
- get: {
6551
+ post: {
5867
6552
  parameters: {
5868
- query: {
5869
- token: string;
5870
- };
6553
+ query?: never;
5871
6554
  header?: never;
5872
6555
  path?: never;
5873
6556
  cookie?: never;
5874
6557
  };
5875
6558
  requestBody?: never;
5876
6559
  responses: {
5877
- /** @description Current trial status */
5878
- 200: {
6560
+ /** @description The rented session */
6561
+ 201: {
5879
6562
  headers: {
5880
6563
  [name: string]: unknown;
5881
6564
  };
5882
6565
  content: {
5883
- "application/json": components["schemas"]["TrialStatusEnvelope"];
6566
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
5884
6567
  };
5885
6568
  };
5886
6569
  /** @description Bad request — validation failed */
@@ -5892,26 +6575,8 @@ export interface paths {
5892
6575
  "application/json": components["schemas"]["ErrorEnvelope"];
5893
6576
  };
5894
6577
  };
5895
- /** @description Not found */
5896
- 404: {
5897
- headers: {
5898
- [name: string]: unknown;
5899
- };
5900
- content: {
5901
- "application/json": components["schemas"]["ErrorEnvelope"];
5902
- };
5903
- };
5904
- /** @description Too many requests — rate limit exceeded */
5905
- 429: {
5906
- headers: {
5907
- [name: string]: unknown;
5908
- };
5909
- content: {
5910
- "application/json": components["schemas"]["ErrorEnvelope"];
5911
- };
5912
- };
5913
- /** @description Internal server error */
5914
- 500: {
6578
+ /** @description Unauthenticated — missing or invalid bearer token */
6579
+ 401: {
5915
6580
  headers: {
5916
6581
  [name: string]: unknown;
5917
6582
  };
@@ -5919,47 +6584,8 @@ export interface paths {
5919
6584
  "application/json": components["schemas"]["ErrorEnvelope"];
5920
6585
  };
5921
6586
  };
5922
- };
5923
- };
5924
- put?: never;
5925
- post?: never;
5926
- delete?: never;
5927
- options?: never;
5928
- head?: never;
5929
- patch?: never;
5930
- trace?: never;
5931
- };
5932
- "/v1/trial/activity": {
5933
- parameters: {
5934
- query?: never;
5935
- header?: never;
5936
- path?: never;
5937
- cookie?: never;
5938
- };
5939
- /**
5940
- * Get trial activity counters
5941
- * @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).
5942
- */
5943
- get: {
5944
- parameters: {
5945
- query?: never;
5946
- header?: never;
5947
- path?: never;
5948
- cookie?: never;
5949
- };
5950
- requestBody?: never;
5951
- responses: {
5952
- /** @description Trial activity snapshot */
5953
- 200: {
5954
- headers: {
5955
- [name: string]: unknown;
5956
- };
5957
- content: {
5958
- "application/json": components["schemas"]["TrialActivityEnvelope"];
5959
- };
5960
- };
5961
- /** @description Bad request — validation failed */
5962
- 400: {
6587
+ /** @description Forbidden — caller authenticated but not allowed */
6588
+ 403: {
5963
6589
  headers: {
5964
6590
  [name: string]: unknown;
5965
6591
  };
@@ -5967,8 +6593,8 @@ export interface paths {
5967
6593
  "application/json": components["schemas"]["ErrorEnvelope"];
5968
6594
  };
5969
6595
  };
5970
- /** @description Unauthenticated — missing or invalid bearer token */
5971
- 401: {
6596
+ /** @description Conflict — concurrent or terminal state */
6597
+ 409: {
5972
6598
  headers: {
5973
6599
  [name: string]: unknown;
5974
6600
  };
@@ -5996,15 +6622,13 @@ export interface paths {
5996
6622
  };
5997
6623
  };
5998
6624
  };
5999
- put?: never;
6000
- post?: never;
6001
6625
  delete?: never;
6002
6626
  options?: never;
6003
6627
  head?: never;
6004
6628
  patch?: never;
6005
6629
  trace?: never;
6006
6630
  };
6007
- "/v1/trial/{trial_id}/revoke": {
6631
+ "/v1/macos-sessions/{id}/cancel": {
6008
6632
  parameters: {
6009
6633
  query?: never;
6010
6634
  header?: never;
@@ -6014,32 +6638,27 @@ export interface paths {
6014
6638
  get?: never;
6015
6639
  put?: never;
6016
6640
  /**
6017
- * Revoke a trial session
6018
- * @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`.
6019
6643
  */
6020
6644
  post: {
6021
6645
  parameters: {
6022
6646
  query?: never;
6023
6647
  header?: never;
6024
6648
  path: {
6025
- /** @description The trl_<base62> public id returned by POST /v1/trial/init. */
6026
- trial_id: string;
6649
+ id: string;
6027
6650
  };
6028
6651
  cookie?: never;
6029
6652
  };
6030
- requestBody?: {
6031
- content: {
6032
- "application/json": components["schemas"]["TrialRevokeBody"];
6033
- };
6034
- };
6653
+ requestBody?: never;
6035
6654
  responses: {
6036
- /** @description Trial revoked (idempotent) */
6655
+ /** @description The cancelled session */
6037
6656
  200: {
6038
6657
  headers: {
6039
6658
  [name: string]: unknown;
6040
6659
  };
6041
6660
  content: {
6042
- "application/json": components["schemas"]["TrialRevokeEnvelope"];
6661
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
6043
6662
  };
6044
6663
  };
6045
6664
  /** @description Bad request — validation failed */
@@ -6060,15 +6679,6 @@ export interface paths {
6060
6679
  "application/json": components["schemas"]["ErrorEnvelope"];
6061
6680
  };
6062
6681
  };
6063
- /** @description Forbidden — caller authenticated but not allowed */
6064
- 403: {
6065
- headers: {
6066
- [name: string]: unknown;
6067
- };
6068
- content: {
6069
- "application/json": components["schemas"]["ErrorEnvelope"];
6070
- };
6071
- };
6072
6682
  /** @description Not found */
6073
6683
  404: {
6074
6684
  headers: {
@@ -6078,8 +6688,8 @@ export interface paths {
6078
6688
  "application/json": components["schemas"]["ErrorEnvelope"];
6079
6689
  };
6080
6690
  };
6081
- /** @description Unprocessable — domain rule violation */
6082
- 422: {
6691
+ /** @description Conflict — concurrent or terminal state */
6692
+ 409: {
6083
6693
  headers: {
6084
6694
  [name: string]: unknown;
6085
6695
  };
@@ -6113,7 +6723,7 @@ export interface paths {
6113
6723
  patch?: never;
6114
6724
  trace?: never;
6115
6725
  };
6116
- "/v1/byod/pair": {
6726
+ "/v1/macos-sessions/{id}/uncancel": {
6117
6727
  parameters: {
6118
6728
  query?: never;
6119
6729
  header?: never;
@@ -6123,29 +6733,27 @@ export interface paths {
6123
6733
  get?: never;
6124
6734
  put?: never;
6125
6735
  /**
6126
- * Start a BYOD pairing flow
6127
- * @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.
6128
6738
  */
6129
6739
  post: {
6130
6740
  parameters: {
6131
6741
  query?: never;
6132
6742
  header?: never;
6133
- path?: never;
6134
- cookie?: never;
6135
- };
6136
- requestBody?: {
6137
- content: {
6138
- "application/json": components["schemas"]["ByodPairBody"];
6743
+ path: {
6744
+ id: string;
6139
6745
  };
6746
+ cookie?: never;
6140
6747
  };
6748
+ requestBody?: never;
6141
6749
  responses: {
6142
- /** @description Activation code + bridge download URL */
6143
- 201: {
6750
+ /** @description The session, no longer cancelled */
6751
+ 200: {
6144
6752
  headers: {
6145
6753
  [name: string]: unknown;
6146
6754
  };
6147
6755
  content: {
6148
- "application/json": components["schemas"]["ByodPairEnvelope"];
6756
+ "application/json": components["schemas"]["MacosSessionEnvelope"];
6149
6757
  };
6150
6758
  };
6151
6759
  /** @description Bad request — validation failed */
@@ -6175,6 +6783,15 @@ export interface paths {
6175
6783
  "application/json": components["schemas"]["ErrorEnvelope"];
6176
6784
  };
6177
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
+ };
6178
6795
  /** @description Too many requests — rate limit exceeded */
6179
6796
  429: {
6180
6797
  headers: {
@@ -6332,6 +6949,10 @@ export interface components {
6332
6949
  stripe_subscription_id: string | null;
6333
6950
  activated_at: string | null;
6334
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;
6335
6956
  created_at: string;
6336
6957
  metadata: {
6337
6958
  [key: string]: unknown;
@@ -6342,6 +6963,31 @@ export interface components {
6342
6963
  shared: boolean;
6343
6964
  fulfillment_status?: string | null;
6344
6965
  };
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;
6990
+ };
6345
6991
  ApiKey: {
6346
6992
  /** Format: uuid */
6347
6993
  id: string;
@@ -6397,6 +7043,14 @@ export interface components {
6397
7043
  TerminateLine: {
6398
7044
  reason?: string;
6399
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
+ };
6400
7054
  PatchLine: {
6401
7055
  metadata: {
6402
7056
  [key: string]: unknown;
@@ -6897,7 +7551,7 @@ export interface components {
6897
7551
  * @description A concrete event type (e.g. `message.received`), a family wildcard (`line.*`), or the catch-all `*`.
6898
7552
  * @enum {string}
6899
7553
  */
6900
- 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" | "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.*";
6901
7555
  WebhookList: {
6902
7556
  webhooks: components["schemas"]["Webhook"][];
6903
7557
  };
@@ -7310,6 +7964,13 @@ export interface components {
7310
7964
  [key: string]: unknown;
7311
7965
  };
7312
7966
  };
7967
+ MacosSessionEnvelope: {
7968
+ /** @enum {boolean} */
7969
+ success: true;
7970
+ data: components["schemas"]["MacosSession"];
7971
+ trace_id: string;
7972
+ request_id: string;
7973
+ };
7313
7974
  AppManifestEnvelope: {
7314
7975
  /** @enum {boolean} */
7315
7976
  success: true;