@medalsocial/sdk 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -298,6 +298,36 @@ await medal.webhooks.test(endpoint.id); // queues a 'test.ping' delivery
298
298
 
299
299
  Failed deliveries retry with exponential backoff (up to 6 attempts) before being dead-lettered.
300
300
 
301
+ ### Channels (partner connect)
302
+
303
+ Mint hosted connect links that let an external person — e.g. a partner's operator, with no Medal account — attach a channel account (today `telegram_inbox`) to the workspace's helpdesk, then track and disconnect the resulting connections. Requires the `channel.connect.manage` scope; OAuth callers additionally need the workspace `admin` role for the writes.
304
+
305
+ ```ts
306
+ // Mint a single-use hosted connect link. `data.url` carries the one-time link
307
+ // token EXACTLY ONCE — an idempotent replay (same Idempotency-Key) omits it,
308
+ // so store it immediately (or revoke and mint a new link if lost).
309
+ const { data: link } = await medal.channels.connectLinks.create(
310
+ {
311
+ channel_type: 'telegram_inbox',
312
+ label: 'Acme support', // shown on the hosted page
313
+ redirect_url: 'https://partner.example.com/done', // optional, https only
314
+ },
315
+ { idempotencyKey: crypto.randomUUID() },
316
+ );
317
+ console.log(link.url); // send this to the person who should connect
318
+
319
+ // Track links (tokens are never returned) and revoke unused ones
320
+ const { data: links } = await medal.channels.connectLinks.list({ status: 'pending' });
321
+ await medal.channels.connectLinks.revoke(link.id);
322
+
323
+ // List the workspace's channel connections and disconnect one
324
+ const { data: connections } = await medal.channels.connections.list();
325
+ // state: 'connecting' | 'active' | 'disconnected' | 'disabled'
326
+ await medal.channels.connections.disconnect(connections[0].id);
327
+ ```
328
+
329
+ When the person completes the hosted sign-in, the link flips to `consumed` and your webhook endpoint receives `helpdesk.channel_connected` (subscribe via the Webhooks resource above); disconnects emit `helpdesk.channel_disconnected` with a `reason`. Inbound messages on the connected account then flow into the helpdesk — consume them via `helpdesk.message_received` and reply with `medal.helpdesk.replies.create`.
330
+
301
331
  ### Workspaces
302
332
 
303
333
  ```ts
@@ -365,7 +395,9 @@ export async function handleWebhook(request: Request): Promise<Response> {
365
395
  }
366
396
  ```
367
397
 
368
- Event types: `helpdesk.conversation_created`, `helpdesk.conversation_assigned`, `helpdesk.conversation_status_changed`, `helpdesk.message_received`, `helpdesk.message_sent`, `helpdesk.message_delivery_updated`, and `test.ping`. All are discriminated on `event.type` — TypeScript narrows `event.data` automatically in a `switch`.
398
+ Event types: `helpdesk.conversation_created`, `helpdesk.conversation_assigned`, `helpdesk.conversation_status_changed`, `helpdesk.message_received`, `helpdesk.message_sent`, `helpdesk.message_delivery_updated`, `helpdesk.channel_connected`, `helpdesk.channel_disconnected`, and `test.ping`. All are discriminated on `event.type` — TypeScript narrows `event.data` automatically in a `switch`.
399
+
400
+ Channel lifecycle events (`helpdesk.channel_connected` / `helpdesk.channel_disconnected`) fire when a channel account is attached to or removed from the workspace — e.g. via a partner connect link (see the Channels resource above). Their `data` is channel-generic: `channel`, `channelConnectionId`, `channel_type`, `connection_ref`, `label`, `masked_identity`, and (disconnect only) `reason` — one of `api_disconnect`, `user_revoked`, `member_disconnect`.
369
401
 
370
402
  Notes:
371
403
 
@@ -53,6 +53,10 @@
53
53
  {
54
54
  "name": "Webhooks",
55
55
  "description": "Manage webhook endpoints and inspect their deliveries."
56
+ },
57
+ {
58
+ "name": "Channels",
59
+ "description": "Partner channel connect — mint hosted connect links and manage the resulting channel connections."
56
60
  }
57
61
  ],
58
62
  "paths": {
@@ -1576,6 +1580,164 @@
1576
1580
  }
1577
1581
  }
1578
1582
  }
1583
+ },
1584
+ "/api/v1/channels/connect-links": {
1585
+ "post": {
1586
+ "tags": [
1587
+ "Channels"
1588
+ ],
1589
+ "operationId": "createChannelConnectLink",
1590
+ "summary": "Mint a hosted connect link",
1591
+ "description": "Mints a single-use hosted connect link that lets an external person (no Medal account required) attach a channel account (e.g. `telegram_inbox`) to the workspace's helpdesk. The response's `data.url` contains the one-time link token EXACTLY ONCE — an idempotent replay (same `Idempotency-Key`) returns the link WITHOUT `url`. Requires the `channel.connect.manage` scope; OAuth callers additionally need the workspace `admin` role.",
1592
+ "requestBody": {
1593
+ "required": true,
1594
+ "content": {
1595
+ "application/json": {
1596
+ "schema": {
1597
+ "$ref": "#/components/schemas/CreateConnectLinkInput"
1598
+ }
1599
+ }
1600
+ }
1601
+ },
1602
+ "responses": {
1603
+ "201": {
1604
+ "description": "Minted connect link, including the one-time `url`.",
1605
+ "content": {
1606
+ "application/json": {
1607
+ "schema": {
1608
+ "$ref": "#/components/schemas/ApiResponse_ConnectLinkCreateResult"
1609
+ }
1610
+ }
1611
+ }
1612
+ },
1613
+ "default": {
1614
+ "$ref": "#/components/responses/ApiError"
1615
+ }
1616
+ }
1617
+ },
1618
+ "get": {
1619
+ "tags": [
1620
+ "Channels"
1621
+ ],
1622
+ "operationId": "listChannelConnectLinks",
1623
+ "summary": "List connect links",
1624
+ "description": "Link tokens are never returned.",
1625
+ "parameters": [
1626
+ {
1627
+ "name": "channel_type",
1628
+ "in": "query",
1629
+ "schema": {
1630
+ "type": "string"
1631
+ }
1632
+ },
1633
+ {
1634
+ "name": "status",
1635
+ "in": "query",
1636
+ "schema": {
1637
+ "$ref": "#/components/schemas/ConnectLinkStatus"
1638
+ }
1639
+ }
1640
+ ],
1641
+ "responses": {
1642
+ "200": {
1643
+ "description": "The workspace's connect links.",
1644
+ "content": {
1645
+ "application/json": {
1646
+ "schema": {
1647
+ "$ref": "#/components/schemas/ApiResponse_ConnectLinkArray"
1648
+ }
1649
+ }
1650
+ }
1651
+ },
1652
+ "default": {
1653
+ "$ref": "#/components/responses/ApiError"
1654
+ }
1655
+ }
1656
+ }
1657
+ },
1658
+ "/api/v1/channels/connect-links/{id}": {
1659
+ "parameters": [
1660
+ {
1661
+ "$ref": "#/components/parameters/Id"
1662
+ }
1663
+ ],
1664
+ "delete": {
1665
+ "tags": [
1666
+ "Channels"
1667
+ ],
1668
+ "operationId": "revokeChannelConnectLink",
1669
+ "summary": "Revoke a connect link",
1670
+ "description": "Revokes a pending connect link so it can no longer be consumed. OAuth callers need the workspace `admin` role.",
1671
+ "responses": {
1672
+ "200": {
1673
+ "description": "Revoke result.",
1674
+ "content": {
1675
+ "application/json": {
1676
+ "schema": {
1677
+ "$ref": "#/components/schemas/ApiResponse_ConnectLinkRevokeResult"
1678
+ }
1679
+ }
1680
+ }
1681
+ },
1682
+ "default": {
1683
+ "$ref": "#/components/responses/ApiError"
1684
+ }
1685
+ }
1686
+ }
1687
+ },
1688
+ "/api/v1/channels/connections": {
1689
+ "get": {
1690
+ "tags": [
1691
+ "Channels"
1692
+ ],
1693
+ "operationId": "listChannelConnections",
1694
+ "summary": "List channel connections",
1695
+ "responses": {
1696
+ "200": {
1697
+ "description": "The workspace's channel connections (generic shape).",
1698
+ "content": {
1699
+ "application/json": {
1700
+ "schema": {
1701
+ "$ref": "#/components/schemas/ApiResponse_ChannelConnectionArray"
1702
+ }
1703
+ }
1704
+ }
1705
+ },
1706
+ "default": {
1707
+ "$ref": "#/components/responses/ApiError"
1708
+ }
1709
+ }
1710
+ }
1711
+ },
1712
+ "/api/v1/channels/connections/{id}": {
1713
+ "parameters": [
1714
+ {
1715
+ "$ref": "#/components/parameters/Id"
1716
+ }
1717
+ ],
1718
+ "delete": {
1719
+ "tags": [
1720
+ "Channels"
1721
+ ],
1722
+ "operationId": "disconnectChannelConnection",
1723
+ "summary": "Disconnect a channel connection",
1724
+ "description": "Disconnects a connected channel account (best-effort platform logout, then local revoke). Emits a `helpdesk.channel_disconnected` webhook event with `reason: \"api_disconnect\"` if the account was previously connected. OAuth callers need the workspace `admin` role.",
1725
+ "responses": {
1726
+ "200": {
1727
+ "description": "Disconnect result.",
1728
+ "content": {
1729
+ "application/json": {
1730
+ "schema": {
1731
+ "$ref": "#/components/schemas/ApiResponse_ChannelConnectionDisconnectResult"
1732
+ }
1733
+ }
1734
+ }
1735
+ },
1736
+ "default": {
1737
+ "$ref": "#/components/responses/ApiError"
1738
+ }
1739
+ }
1740
+ }
1579
1741
  }
1580
1742
  },
1581
1743
  "components": {
@@ -4052,6 +4214,207 @@
4052
4214
  }
4053
4215
  }
4054
4216
  },
4217
+ "ConnectLinkStatus": {
4218
+ "type": "string",
4219
+ "enum": [
4220
+ "pending",
4221
+ "consumed",
4222
+ "expired",
4223
+ "revoked"
4224
+ ]
4225
+ },
4226
+ "ChannelConnectionState": {
4227
+ "type": "string",
4228
+ "enum": [
4229
+ "connecting",
4230
+ "active",
4231
+ "disconnected",
4232
+ "disabled"
4233
+ ]
4234
+ },
4235
+ "CreateConnectLinkInput": {
4236
+ "type": "object",
4237
+ "required": [
4238
+ "channel_type"
4239
+ ],
4240
+ "properties": {
4241
+ "channel_type": {
4242
+ "type": "string",
4243
+ "maxLength": 64,
4244
+ "description": "Channel type to connect (e.g. `telegram_inbox`)."
4245
+ },
4246
+ "label": {
4247
+ "type": "string",
4248
+ "maxLength": 100,
4249
+ "description": "Display label shown on the hosted connect page."
4250
+ },
4251
+ "redirect_url": {
4252
+ "type": "string",
4253
+ "format": "uri",
4254
+ "maxLength": 2000,
4255
+ "description": "URL the hosted page redirects to after a successful connect — must be https."
4256
+ }
4257
+ }
4258
+ },
4259
+ "ConnectLinkCreateResult": {
4260
+ "type": "object",
4261
+ "required": [
4262
+ "id",
4263
+ "channel_type",
4264
+ "label",
4265
+ "status",
4266
+ "expires_at"
4267
+ ],
4268
+ "properties": {
4269
+ "id": {
4270
+ "type": "string"
4271
+ },
4272
+ "url": {
4273
+ "type": "string",
4274
+ "format": "uri",
4275
+ "description": "Single-use hosted connect URL containing the one-time link token — present ONLY in the live create response. An idempotent replay of the create request omits it; the token can never be retrieved again."
4276
+ },
4277
+ "channel_type": {
4278
+ "type": "string"
4279
+ },
4280
+ "label": {
4281
+ "type": [
4282
+ "string",
4283
+ "null"
4284
+ ]
4285
+ },
4286
+ "status": {
4287
+ "$ref": "#/components/schemas/ConnectLinkStatus"
4288
+ },
4289
+ "expires_at": {
4290
+ "type": "integer",
4291
+ "description": "Unix timestamp in milliseconds."
4292
+ }
4293
+ }
4294
+ },
4295
+ "ConnectLink": {
4296
+ "type": "object",
4297
+ "required": [
4298
+ "id",
4299
+ "channel_type",
4300
+ "label",
4301
+ "status",
4302
+ "consumed_connection_ref",
4303
+ "expires_at",
4304
+ "created_at"
4305
+ ],
4306
+ "properties": {
4307
+ "id": {
4308
+ "type": "string"
4309
+ },
4310
+ "channel_type": {
4311
+ "type": "string"
4312
+ },
4313
+ "label": {
4314
+ "type": [
4315
+ "string",
4316
+ "null"
4317
+ ]
4318
+ },
4319
+ "status": {
4320
+ "$ref": "#/components/schemas/ConnectLinkStatus"
4321
+ },
4322
+ "consumed_connection_ref": {
4323
+ "type": [
4324
+ "string",
4325
+ "null"
4326
+ ],
4327
+ "description": "Stable ref of the connection created by consuming this link, or `null`."
4328
+ },
4329
+ "expires_at": {
4330
+ "type": "integer",
4331
+ "description": "Unix timestamp in milliseconds."
4332
+ },
4333
+ "created_at": {
4334
+ "type": "integer",
4335
+ "description": "Unix timestamp in milliseconds."
4336
+ }
4337
+ }
4338
+ },
4339
+ "ConnectLinkRevokeResult": {
4340
+ "type": "object",
4341
+ "required": [
4342
+ "id",
4343
+ "status"
4344
+ ],
4345
+ "properties": {
4346
+ "id": {
4347
+ "type": "string"
4348
+ },
4349
+ "status": {
4350
+ "type": "string",
4351
+ "const": "revoked"
4352
+ }
4353
+ }
4354
+ },
4355
+ "ChannelConnection": {
4356
+ "type": "object",
4357
+ "required": [
4358
+ "id",
4359
+ "channel_type",
4360
+ "label",
4361
+ "state",
4362
+ "masked_identity",
4363
+ "last_activity_at",
4364
+ "helpdesk_connection_id"
4365
+ ],
4366
+ "properties": {
4367
+ "id": {
4368
+ "type": "string"
4369
+ },
4370
+ "channel_type": {
4371
+ "type": "string"
4372
+ },
4373
+ "label": {
4374
+ "type": [
4375
+ "string",
4376
+ "null"
4377
+ ]
4378
+ },
4379
+ "state": {
4380
+ "$ref": "#/components/schemas/ChannelConnectionState"
4381
+ },
4382
+ "masked_identity": {
4383
+ "type": "string",
4384
+ "description": "Privacy-preserving identity handle (e.g. a masked phone number)."
4385
+ },
4386
+ "last_activity_at": {
4387
+ "type": [
4388
+ "integer",
4389
+ "null"
4390
+ ],
4391
+ "description": "Unix timestamp in milliseconds, or `null` if never active."
4392
+ },
4393
+ "helpdesk_connection_id": {
4394
+ "type": [
4395
+ "string",
4396
+ "null"
4397
+ ],
4398
+ "description": "Linked helpdesk channel connection ID, or `null`."
4399
+ }
4400
+ }
4401
+ },
4402
+ "ChannelConnectionDisconnectResult": {
4403
+ "type": "object",
4404
+ "required": [
4405
+ "id",
4406
+ "state"
4407
+ ],
4408
+ "properties": {
4409
+ "id": {
4410
+ "type": "string"
4411
+ },
4412
+ "state": {
4413
+ "type": "string",
4414
+ "const": "disconnected"
4415
+ }
4416
+ }
4417
+ },
4055
4418
  "ApiResponse_PostCreateResult": {
4056
4419
  "$ref": "#/components/schemas/Envelope_PostCreateResult"
4057
4420
  },
@@ -4157,6 +4520,21 @@
4157
4520
  "ApiResponse_WebhookTestResult": {
4158
4521
  "$ref": "#/components/schemas/Envelope_WebhookTestResult"
4159
4522
  },
4523
+ "ApiResponse_ConnectLinkCreateResult": {
4524
+ "$ref": "#/components/schemas/Envelope_ConnectLinkCreateResult"
4525
+ },
4526
+ "ApiResponse_ConnectLinkArray": {
4527
+ "$ref": "#/components/schemas/Envelope_ConnectLinkArray"
4528
+ },
4529
+ "ApiResponse_ConnectLinkRevokeResult": {
4530
+ "$ref": "#/components/schemas/Envelope_ConnectLinkRevokeResult"
4531
+ },
4532
+ "ApiResponse_ChannelConnectionArray": {
4533
+ "$ref": "#/components/schemas/Envelope_ChannelConnectionArray"
4534
+ },
4535
+ "ApiResponse_ChannelConnectionDisconnectResult": {
4536
+ "$ref": "#/components/schemas/Envelope_ChannelConnectionDisconnectResult"
4537
+ },
4160
4538
  "PaginatedResponse_Post": {
4161
4539
  "type": "object",
4162
4540
  "required": [
@@ -4670,6 +5048,67 @@
4670
5048
  "$ref": "#/components/schemas/WebhookTestResult"
4671
5049
  }
4672
5050
  }
5051
+ },
5052
+ "Envelope_ConnectLinkCreateResult": {
5053
+ "type": "object",
5054
+ "required": [
5055
+ "data"
5056
+ ],
5057
+ "properties": {
5058
+ "data": {
5059
+ "$ref": "#/components/schemas/ConnectLinkCreateResult"
5060
+ }
5061
+ }
5062
+ },
5063
+ "Envelope_ConnectLinkArray": {
5064
+ "type": "object",
5065
+ "required": [
5066
+ "data"
5067
+ ],
5068
+ "properties": {
5069
+ "data": {
5070
+ "type": "array",
5071
+ "items": {
5072
+ "$ref": "#/components/schemas/ConnectLink"
5073
+ }
5074
+ }
5075
+ }
5076
+ },
5077
+ "Envelope_ConnectLinkRevokeResult": {
5078
+ "type": "object",
5079
+ "required": [
5080
+ "data"
5081
+ ],
5082
+ "properties": {
5083
+ "data": {
5084
+ "$ref": "#/components/schemas/ConnectLinkRevokeResult"
5085
+ }
5086
+ }
5087
+ },
5088
+ "Envelope_ChannelConnectionArray": {
5089
+ "type": "object",
5090
+ "required": [
5091
+ "data"
5092
+ ],
5093
+ "properties": {
5094
+ "data": {
5095
+ "type": "array",
5096
+ "items": {
5097
+ "$ref": "#/components/schemas/ChannelConnection"
5098
+ }
5099
+ }
5100
+ }
5101
+ },
5102
+ "Envelope_ChannelConnectionDisconnectResult": {
5103
+ "type": "object",
5104
+ "required": [
5105
+ "data"
5106
+ ],
5107
+ "properties": {
5108
+ "data": {
5109
+ "$ref": "#/components/schemas/ChannelConnectionDisconnectResult"
5110
+ }
5111
+ }
4673
5112
  }
4674
5113
  }
4675
5114
  }