airweave-sdk 0.8.64__py3-none-any.whl → 0.8.66__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. airweave/__init__.py +44 -38
  2. airweave/client.py +19 -16
  3. airweave/collections/__init__.py +3 -6
  4. airweave/collections/client.py +273 -113
  5. airweave/collections/raw_client.py +633 -94
  6. airweave/collections/types/__init__.py +2 -4
  7. airweave/core/client_wrapper.py +4 -30
  8. airweave/errors/__init__.py +10 -2
  9. airweave/errors/conflict_error.py +11 -0
  10. airweave/errors/not_found_error.py +11 -0
  11. airweave/errors/too_many_requests_error.py +11 -0
  12. airweave/errors/unprocessable_entity_error.py +1 -2
  13. airweave/{types/message_status.py → events/__init__.py} +2 -1
  14. airweave/events/client.py +919 -0
  15. airweave/events/raw_client.py +1435 -0
  16. airweave/source_connections/client.py +210 -162
  17. airweave/source_connections/raw_client.py +574 -137
  18. airweave/sources/client.py +42 -18
  19. airweave/sources/raw_client.py +118 -17
  20. airweave/types/__init__.py +33 -33
  21. airweave/types/{create_subscription_request.py → conflict_error_response.py} +9 -6
  22. airweave/types/delivery_attempt.py +61 -0
  23. airweave/types/event_message.py +55 -0
  24. airweave/types/event_message_with_attempts.py +59 -0
  25. airweave/types/{endpoint_secret_out.py → not_found_error_response.py} +9 -2
  26. airweave/types/{subscription_with_attempts_out.py → rate_limit_error_response.py} +9 -6
  27. airweave/types/recovery_task.py +35 -0
  28. airweave/types/search_request.py +13 -10
  29. airweave/types/search_response.py +6 -3
  30. airweave/types/source_connection.py +73 -18
  31. airweave/types/source_connection_job.py +65 -15
  32. airweave/types/source_connection_list_item.py +45 -10
  33. airweave/types/sync_event_payload.py +72 -0
  34. airweave/types/{patch_subscription_request.py → validation_error_detail.py} +16 -5
  35. airweave/types/validation_error_response.py +30 -0
  36. airweave/types/webhook_subscription.py +68 -0
  37. {airweave_sdk-0.8.64.dist-info → airweave_sdk-0.8.66.dist-info}/METADATA +1 -5
  38. {airweave_sdk-0.8.64.dist-info → airweave_sdk-0.8.66.dist-info}/RECORD +39 -34
  39. airweave/collections/types/search_collections_readable_id_search_post_response.py +0 -8
  40. airweave/types/collection_update.py +0 -35
  41. airweave/types/endpoint_out.py +0 -35
  42. airweave/types/message_attempt_out.py +0 -37
  43. airweave/types/message_attempt_trigger_type.py +0 -3
  44. airweave/types/message_out.py +0 -29
  45. airweave/types/message_status_text.py +0 -5
  46. {airweave_sdk-0.8.64.dist-info → airweave_sdk-0.8.66.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1435 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import datetime as dt
4
+ import typing
5
+ from json.decoder import JSONDecodeError
6
+
7
+ from ..core.api_error import ApiError
8
+ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
9
+ from ..core.http_response import AsyncHttpResponse, HttpResponse
10
+ from ..core.jsonable_encoder import jsonable_encoder
11
+ from ..core.pydantic_utilities import parse_obj_as
12
+ from ..core.request_options import RequestOptions
13
+ from ..errors.not_found_error import NotFoundError
14
+ from ..errors.too_many_requests_error import TooManyRequestsError
15
+ from ..errors.unprocessable_entity_error import UnprocessableEntityError
16
+ from ..types.event_message import EventMessage
17
+ from ..types.event_message_with_attempts import EventMessageWithAttempts
18
+ from ..types.event_type import EventType
19
+ from ..types.not_found_error_response import NotFoundErrorResponse
20
+ from ..types.rate_limit_error_response import RateLimitErrorResponse
21
+ from ..types.recovery_task import RecoveryTask
22
+ from ..types.webhook_subscription import WebhookSubscription
23
+
24
+ # this is used as the default value for optional parameters
25
+ OMIT = typing.cast(typing.Any, ...)
26
+
27
+
28
+ class RawEventsClient:
29
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
30
+ self._client_wrapper = client_wrapper
31
+
32
+ def get_messages(
33
+ self,
34
+ *,
35
+ event_types: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
36
+ request_options: typing.Optional[RequestOptions] = None,
37
+ ) -> HttpResponse[typing.List[EventMessage]]:
38
+ """
39
+ Retrieve all event messages for your organization.
40
+
41
+ Event messages represent webhook payloads that were sent (or attempted to be sent)
42
+ to your subscribed endpoints. Each message contains the event type, payload data,
43
+ and delivery status information.
44
+
45
+ Use the `event_types` query parameter to filter messages by specific event types,
46
+ such as `sync.completed` or `sync.failed`.
47
+
48
+ Parameters
49
+ ----------
50
+ event_types : typing.Optional[typing.Union[str, typing.Sequence[str]]]
51
+ Filter messages by event type(s). Accepts multiple values, e.g., `?event_types=sync.completed&event_types=sync.failed`.
52
+
53
+ request_options : typing.Optional[RequestOptions]
54
+ Request-specific configuration.
55
+
56
+ Returns
57
+ -------
58
+ HttpResponse[typing.List[EventMessage]]
59
+ List of event messages
60
+ """
61
+ _response = self._client_wrapper.httpx_client.request(
62
+ "events/messages",
63
+ method="GET",
64
+ params={
65
+ "event_types": event_types,
66
+ },
67
+ request_options=request_options,
68
+ )
69
+ try:
70
+ if 200 <= _response.status_code < 300:
71
+ _data = typing.cast(
72
+ typing.List[EventMessage],
73
+ parse_obj_as(
74
+ type_=typing.List[EventMessage], # type: ignore
75
+ object_=_response.json(),
76
+ ),
77
+ )
78
+ return HttpResponse(response=_response, data=_data)
79
+ if _response.status_code == 422:
80
+ raise UnprocessableEntityError(
81
+ headers=dict(_response.headers),
82
+ body=typing.cast(
83
+ typing.Optional[typing.Any],
84
+ parse_obj_as(
85
+ type_=typing.Optional[typing.Any], # type: ignore
86
+ object_=_response.json(),
87
+ ),
88
+ ),
89
+ )
90
+ if _response.status_code == 429:
91
+ raise TooManyRequestsError(
92
+ headers=dict(_response.headers),
93
+ body=typing.cast(
94
+ RateLimitErrorResponse,
95
+ parse_obj_as(
96
+ type_=RateLimitErrorResponse, # type: ignore
97
+ object_=_response.json(),
98
+ ),
99
+ ),
100
+ )
101
+ _response_json = _response.json()
102
+ except JSONDecodeError:
103
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
104
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
105
+
106
+ def get_message(
107
+ self,
108
+ message_id: str,
109
+ *,
110
+ include_attempts: typing.Optional[bool] = None,
111
+ request_options: typing.Optional[RequestOptions] = None,
112
+ ) -> HttpResponse[EventMessageWithAttempts]:
113
+ """
114
+ Retrieve a specific event message by its ID.
115
+
116
+ Returns the full message details including the event type, payload data,
117
+ timestamp, and delivery channel information. Use this to inspect the
118
+ exact payload that was sent to your webhook endpoints.
119
+
120
+ Use `include_attempts=true` to also retrieve delivery attempts for this message,
121
+ which include HTTP response codes, response bodies, and timestamps for debugging
122
+ delivery failures.
123
+
124
+ Parameters
125
+ ----------
126
+ message_id : str
127
+ The unique identifier of the message to retrieve (UUID).
128
+
129
+ include_attempts : typing.Optional[bool]
130
+ Include delivery attempts for this message. Each attempt includes the HTTP response code, response body, and timestamp.
131
+
132
+ request_options : typing.Optional[RequestOptions]
133
+ Request-specific configuration.
134
+
135
+ Returns
136
+ -------
137
+ HttpResponse[EventMessageWithAttempts]
138
+ Event message details
139
+ """
140
+ _response = self._client_wrapper.httpx_client.request(
141
+ f"events/messages/{jsonable_encoder(message_id)}",
142
+ method="GET",
143
+ params={
144
+ "include_attempts": include_attempts,
145
+ },
146
+ request_options=request_options,
147
+ )
148
+ try:
149
+ if 200 <= _response.status_code < 300:
150
+ _data = typing.cast(
151
+ EventMessageWithAttempts,
152
+ parse_obj_as(
153
+ type_=EventMessageWithAttempts, # type: ignore
154
+ object_=_response.json(),
155
+ ),
156
+ )
157
+ return HttpResponse(response=_response, data=_data)
158
+ if _response.status_code == 404:
159
+ raise NotFoundError(
160
+ headers=dict(_response.headers),
161
+ body=typing.cast(
162
+ NotFoundErrorResponse,
163
+ parse_obj_as(
164
+ type_=NotFoundErrorResponse, # type: ignore
165
+ object_=_response.json(),
166
+ ),
167
+ ),
168
+ )
169
+ if _response.status_code == 422:
170
+ raise UnprocessableEntityError(
171
+ headers=dict(_response.headers),
172
+ body=typing.cast(
173
+ typing.Optional[typing.Any],
174
+ parse_obj_as(
175
+ type_=typing.Optional[typing.Any], # type: ignore
176
+ object_=_response.json(),
177
+ ),
178
+ ),
179
+ )
180
+ if _response.status_code == 429:
181
+ raise TooManyRequestsError(
182
+ headers=dict(_response.headers),
183
+ body=typing.cast(
184
+ RateLimitErrorResponse,
185
+ parse_obj_as(
186
+ type_=RateLimitErrorResponse, # type: ignore
187
+ object_=_response.json(),
188
+ ),
189
+ ),
190
+ )
191
+ _response_json = _response.json()
192
+ except JSONDecodeError:
193
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
194
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
195
+
196
+ def get_subscriptions(
197
+ self, *, request_options: typing.Optional[RequestOptions] = None
198
+ ) -> HttpResponse[typing.List[WebhookSubscription]]:
199
+ """
200
+ List all webhook subscriptions for your organization.
201
+
202
+ Returns all configured webhook endpoints, including their URLs, subscribed
203
+ event types, and current status (enabled/disabled). Use this to audit
204
+ your webhook configuration or find a specific subscription.
205
+
206
+ Parameters
207
+ ----------
208
+ request_options : typing.Optional[RequestOptions]
209
+ Request-specific configuration.
210
+
211
+ Returns
212
+ -------
213
+ HttpResponse[typing.List[WebhookSubscription]]
214
+ List of webhook subscriptions
215
+ """
216
+ _response = self._client_wrapper.httpx_client.request(
217
+ "events/subscriptions",
218
+ method="GET",
219
+ request_options=request_options,
220
+ )
221
+ try:
222
+ if 200 <= _response.status_code < 300:
223
+ _data = typing.cast(
224
+ typing.List[WebhookSubscription],
225
+ parse_obj_as(
226
+ type_=typing.List[WebhookSubscription], # type: ignore
227
+ object_=_response.json(),
228
+ ),
229
+ )
230
+ return HttpResponse(response=_response, data=_data)
231
+ if _response.status_code == 422:
232
+ raise UnprocessableEntityError(
233
+ headers=dict(_response.headers),
234
+ body=typing.cast(
235
+ typing.Optional[typing.Any],
236
+ parse_obj_as(
237
+ type_=typing.Optional[typing.Any], # type: ignore
238
+ object_=_response.json(),
239
+ ),
240
+ ),
241
+ )
242
+ if _response.status_code == 429:
243
+ raise TooManyRequestsError(
244
+ headers=dict(_response.headers),
245
+ body=typing.cast(
246
+ RateLimitErrorResponse,
247
+ parse_obj_as(
248
+ type_=RateLimitErrorResponse, # type: ignore
249
+ object_=_response.json(),
250
+ ),
251
+ ),
252
+ )
253
+ _response_json = _response.json()
254
+ except JSONDecodeError:
255
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
256
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
257
+
258
+ def create_subscription(
259
+ self,
260
+ *,
261
+ url: str,
262
+ event_types: typing.Sequence[EventType],
263
+ secret: typing.Optional[str] = OMIT,
264
+ request_options: typing.Optional[RequestOptions] = None,
265
+ ) -> HttpResponse[WebhookSubscription]:
266
+ """
267
+ Create a new webhook subscription.
268
+
269
+ Webhook subscriptions allow you to receive real-time notifications when events
270
+ occur in Airweave. When you create a subscription, you specify:
271
+
272
+ - **URL**: The HTTPS endpoint where events will be delivered
273
+ - **Event Types**: Which events you want to receive (e.g., `sync.completed`, `sync.failed`)
274
+ - **Secret** (optional): A custom signing secret for verifying webhook signatures
275
+
276
+ After creation, Airweave will send HTTP POST requests to your URL whenever
277
+ matching events occur. Each request includes a signature header for verification.
278
+
279
+ Parameters
280
+ ----------
281
+ url : str
282
+ The HTTPS URL where webhook events will be delivered. Must be a publicly accessible endpoint that returns a 2xx status code.
283
+
284
+ event_types : typing.Sequence[EventType]
285
+ List of event types to subscribe to. Events not in this list will not be delivered to this subscription. Available types: `sync.pending`, `sync.running`, `sync.completed`, `sync.failed`, `sync.cancelled`.
286
+
287
+ secret : typing.Optional[str]
288
+ Optional custom signing secret for webhook signature verification. If not provided, a secure secret will be auto-generated. Must be at least 24 characters if specified.
289
+
290
+ request_options : typing.Optional[RequestOptions]
291
+ Request-specific configuration.
292
+
293
+ Returns
294
+ -------
295
+ HttpResponse[WebhookSubscription]
296
+ Created subscription
297
+ """
298
+ _response = self._client_wrapper.httpx_client.request(
299
+ "events/subscriptions",
300
+ method="POST",
301
+ json={
302
+ "url": url,
303
+ "event_types": event_types,
304
+ "secret": secret,
305
+ },
306
+ headers={
307
+ "content-type": "application/json",
308
+ },
309
+ request_options=request_options,
310
+ omit=OMIT,
311
+ )
312
+ try:
313
+ if 200 <= _response.status_code < 300:
314
+ _data = typing.cast(
315
+ WebhookSubscription,
316
+ parse_obj_as(
317
+ type_=WebhookSubscription, # type: ignore
318
+ object_=_response.json(),
319
+ ),
320
+ )
321
+ return HttpResponse(response=_response, data=_data)
322
+ if _response.status_code == 422:
323
+ raise UnprocessableEntityError(
324
+ headers=dict(_response.headers),
325
+ body=typing.cast(
326
+ typing.Optional[typing.Any],
327
+ parse_obj_as(
328
+ type_=typing.Optional[typing.Any], # type: ignore
329
+ object_=_response.json(),
330
+ ),
331
+ ),
332
+ )
333
+ if _response.status_code == 429:
334
+ raise TooManyRequestsError(
335
+ headers=dict(_response.headers),
336
+ body=typing.cast(
337
+ RateLimitErrorResponse,
338
+ parse_obj_as(
339
+ type_=RateLimitErrorResponse, # type: ignore
340
+ object_=_response.json(),
341
+ ),
342
+ ),
343
+ )
344
+ _response_json = _response.json()
345
+ except JSONDecodeError:
346
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
347
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
348
+
349
+ def get_subscription(
350
+ self,
351
+ subscription_id: str,
352
+ *,
353
+ include_secret: typing.Optional[bool] = None,
354
+ request_options: typing.Optional[RequestOptions] = None,
355
+ ) -> HttpResponse[WebhookSubscription]:
356
+ """
357
+ Retrieve a specific webhook subscription with its recent delivery attempts.
358
+
359
+ Returns the subscription configuration along with a history of message delivery
360
+ attempts. This is useful for debugging delivery issues or verifying that your
361
+ endpoint is correctly receiving events.
362
+
363
+ Use `include_secret=true` to also retrieve the signing secret for webhook
364
+ signature verification. Keep this secret secure.
365
+
366
+ Parameters
367
+ ----------
368
+ subscription_id : str
369
+ The unique identifier of the subscription to retrieve (UUID).
370
+
371
+ include_secret : typing.Optional[bool]
372
+ Include the signing secret for webhook signature verification. Keep this secret secure and use it to verify the 'svix-signature' header.
373
+
374
+ request_options : typing.Optional[RequestOptions]
375
+ Request-specific configuration.
376
+
377
+ Returns
378
+ -------
379
+ HttpResponse[WebhookSubscription]
380
+ Subscription with delivery attempts
381
+ """
382
+ _response = self._client_wrapper.httpx_client.request(
383
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
384
+ method="GET",
385
+ params={
386
+ "include_secret": include_secret,
387
+ },
388
+ request_options=request_options,
389
+ )
390
+ try:
391
+ if 200 <= _response.status_code < 300:
392
+ _data = typing.cast(
393
+ WebhookSubscription,
394
+ parse_obj_as(
395
+ type_=WebhookSubscription, # type: ignore
396
+ object_=_response.json(),
397
+ ),
398
+ )
399
+ return HttpResponse(response=_response, data=_data)
400
+ if _response.status_code == 404:
401
+ raise NotFoundError(
402
+ headers=dict(_response.headers),
403
+ body=typing.cast(
404
+ NotFoundErrorResponse,
405
+ parse_obj_as(
406
+ type_=NotFoundErrorResponse, # type: ignore
407
+ object_=_response.json(),
408
+ ),
409
+ ),
410
+ )
411
+ if _response.status_code == 422:
412
+ raise UnprocessableEntityError(
413
+ headers=dict(_response.headers),
414
+ body=typing.cast(
415
+ typing.Optional[typing.Any],
416
+ parse_obj_as(
417
+ type_=typing.Optional[typing.Any], # type: ignore
418
+ object_=_response.json(),
419
+ ),
420
+ ),
421
+ )
422
+ if _response.status_code == 429:
423
+ raise TooManyRequestsError(
424
+ headers=dict(_response.headers),
425
+ body=typing.cast(
426
+ RateLimitErrorResponse,
427
+ parse_obj_as(
428
+ type_=RateLimitErrorResponse, # type: ignore
429
+ object_=_response.json(),
430
+ ),
431
+ ),
432
+ )
433
+ _response_json = _response.json()
434
+ except JSONDecodeError:
435
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
436
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
437
+
438
+ def delete_subscription(
439
+ self, subscription_id: str, *, request_options: typing.Optional[RequestOptions] = None
440
+ ) -> HttpResponse[WebhookSubscription]:
441
+ """
442
+ Permanently delete a webhook subscription.
443
+
444
+ Once deleted, Airweave will stop sending events to this endpoint immediately.
445
+ This action cannot be undone. Any pending message deliveries will be cancelled.
446
+
447
+ If you want to temporarily stop receiving events, consider disabling the
448
+ subscription instead using the PATCH endpoint.
449
+
450
+ Parameters
451
+ ----------
452
+ subscription_id : str
453
+ The unique identifier of the subscription to delete (UUID).
454
+
455
+ request_options : typing.Optional[RequestOptions]
456
+ Request-specific configuration.
457
+
458
+ Returns
459
+ -------
460
+ HttpResponse[WebhookSubscription]
461
+ Deleted subscription
462
+ """
463
+ _response = self._client_wrapper.httpx_client.request(
464
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
465
+ method="DELETE",
466
+ request_options=request_options,
467
+ )
468
+ try:
469
+ if 200 <= _response.status_code < 300:
470
+ _data = typing.cast(
471
+ WebhookSubscription,
472
+ parse_obj_as(
473
+ type_=WebhookSubscription, # type: ignore
474
+ object_=_response.json(),
475
+ ),
476
+ )
477
+ return HttpResponse(response=_response, data=_data)
478
+ if _response.status_code == 404:
479
+ raise NotFoundError(
480
+ headers=dict(_response.headers),
481
+ body=typing.cast(
482
+ NotFoundErrorResponse,
483
+ parse_obj_as(
484
+ type_=NotFoundErrorResponse, # type: ignore
485
+ object_=_response.json(),
486
+ ),
487
+ ),
488
+ )
489
+ if _response.status_code == 422:
490
+ raise UnprocessableEntityError(
491
+ headers=dict(_response.headers),
492
+ body=typing.cast(
493
+ typing.Optional[typing.Any],
494
+ parse_obj_as(
495
+ type_=typing.Optional[typing.Any], # type: ignore
496
+ object_=_response.json(),
497
+ ),
498
+ ),
499
+ )
500
+ if _response.status_code == 429:
501
+ raise TooManyRequestsError(
502
+ headers=dict(_response.headers),
503
+ body=typing.cast(
504
+ RateLimitErrorResponse,
505
+ parse_obj_as(
506
+ type_=RateLimitErrorResponse, # type: ignore
507
+ object_=_response.json(),
508
+ ),
509
+ ),
510
+ )
511
+ _response_json = _response.json()
512
+ except JSONDecodeError:
513
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
514
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
515
+
516
+ def patch_subscription(
517
+ self,
518
+ subscription_id: str,
519
+ *,
520
+ url: typing.Optional[str] = OMIT,
521
+ event_types: typing.Optional[typing.Sequence[EventType]] = OMIT,
522
+ disabled: typing.Optional[bool] = OMIT,
523
+ recover_since: typing.Optional[dt.datetime] = OMIT,
524
+ request_options: typing.Optional[RequestOptions] = None,
525
+ ) -> HttpResponse[WebhookSubscription]:
526
+ """
527
+ Update an existing webhook subscription.
528
+
529
+ Use this endpoint to modify a subscription's configuration. You can:
530
+
531
+ - **Change the URL**: Update where events are delivered
532
+ - **Update event types**: Modify which events trigger notifications
533
+ - **Enable/disable**: Temporarily pause delivery without deleting the subscription
534
+ - **Recover messages**: When re-enabling, optionally recover missed messages
535
+
536
+ Only include the fields you want to change. Omitted fields will retain their
537
+ current values.
538
+
539
+ When re-enabling a subscription (`disabled: false`), you can optionally provide
540
+ `recover_since` to automatically retry all messages that were generated while
541
+ the subscription was disabled.
542
+
543
+ Parameters
544
+ ----------
545
+ subscription_id : str
546
+ The unique identifier of the subscription to update (UUID).
547
+
548
+ url : typing.Optional[str]
549
+ New URL for webhook delivery. Must be a publicly accessible HTTPS endpoint.
550
+
551
+ event_types : typing.Optional[typing.Sequence[EventType]]
552
+ New list of event types to subscribe to. This replaces the existing list entirely.
553
+
554
+ disabled : typing.Optional[bool]
555
+ Set to `true` to pause delivery to this subscription, or `false` to resume. Disabled subscriptions will not receive events.
556
+
557
+ recover_since : typing.Optional[dt.datetime]
558
+ When re-enabling a subscription (`disabled: false`), optionally recover failed messages from this timestamp. Only applies when enabling.
559
+
560
+ request_options : typing.Optional[RequestOptions]
561
+ Request-specific configuration.
562
+
563
+ Returns
564
+ -------
565
+ HttpResponse[WebhookSubscription]
566
+ Updated subscription
567
+ """
568
+ _response = self._client_wrapper.httpx_client.request(
569
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
570
+ method="PATCH",
571
+ json={
572
+ "url": url,
573
+ "event_types": event_types,
574
+ "disabled": disabled,
575
+ "recover_since": recover_since,
576
+ },
577
+ headers={
578
+ "content-type": "application/json",
579
+ },
580
+ request_options=request_options,
581
+ omit=OMIT,
582
+ )
583
+ try:
584
+ if 200 <= _response.status_code < 300:
585
+ _data = typing.cast(
586
+ WebhookSubscription,
587
+ parse_obj_as(
588
+ type_=WebhookSubscription, # type: ignore
589
+ object_=_response.json(),
590
+ ),
591
+ )
592
+ return HttpResponse(response=_response, data=_data)
593
+ if _response.status_code == 404:
594
+ raise NotFoundError(
595
+ headers=dict(_response.headers),
596
+ body=typing.cast(
597
+ NotFoundErrorResponse,
598
+ parse_obj_as(
599
+ type_=NotFoundErrorResponse, # type: ignore
600
+ object_=_response.json(),
601
+ ),
602
+ ),
603
+ )
604
+ if _response.status_code == 422:
605
+ raise UnprocessableEntityError(
606
+ headers=dict(_response.headers),
607
+ body=typing.cast(
608
+ typing.Optional[typing.Any],
609
+ parse_obj_as(
610
+ type_=typing.Optional[typing.Any], # type: ignore
611
+ object_=_response.json(),
612
+ ),
613
+ ),
614
+ )
615
+ if _response.status_code == 429:
616
+ raise TooManyRequestsError(
617
+ headers=dict(_response.headers),
618
+ body=typing.cast(
619
+ RateLimitErrorResponse,
620
+ parse_obj_as(
621
+ type_=RateLimitErrorResponse, # type: ignore
622
+ object_=_response.json(),
623
+ ),
624
+ ),
625
+ )
626
+ _response_json = _response.json()
627
+ except JSONDecodeError:
628
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
629
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
630
+
631
+ def recover_failed_messages(
632
+ self,
633
+ subscription_id: str,
634
+ *,
635
+ since: dt.datetime,
636
+ until: typing.Optional[dt.datetime] = OMIT,
637
+ request_options: typing.Optional[RequestOptions] = None,
638
+ ) -> HttpResponse[RecoveryTask]:
639
+ """
640
+ Retry failed message deliveries for a webhook subscription.
641
+
642
+ Triggers a recovery process that replays all failed messages within the
643
+ specified time window. This is useful when:
644
+
645
+ - Your endpoint was temporarily down and you want to catch up
646
+ - You've fixed a bug in your webhook handler
647
+ - You want to reprocess events after re-enabling a disabled subscription
648
+
649
+ Messages are retried in chronological order. Successfully delivered messages
650
+ are skipped; only failed or pending messages are retried.
651
+
652
+ Parameters
653
+ ----------
654
+ subscription_id : str
655
+ The unique identifier of the subscription to recover messages for (UUID).
656
+
657
+ since : dt.datetime
658
+ Start of the recovery time window (inclusive). All failed messages from this time onward will be retried.
659
+
660
+ until : typing.Optional[dt.datetime]
661
+ End of the recovery time window (exclusive). If not specified, recovers all failed messages up to now.
662
+
663
+ request_options : typing.Optional[RequestOptions]
664
+ Request-specific configuration.
665
+
666
+ Returns
667
+ -------
668
+ HttpResponse[RecoveryTask]
669
+ Recovery task information
670
+ """
671
+ _response = self._client_wrapper.httpx_client.request(
672
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}/recover",
673
+ method="POST",
674
+ json={
675
+ "since": since,
676
+ "until": until,
677
+ },
678
+ headers={
679
+ "content-type": "application/json",
680
+ },
681
+ request_options=request_options,
682
+ omit=OMIT,
683
+ )
684
+ try:
685
+ if 200 <= _response.status_code < 300:
686
+ _data = typing.cast(
687
+ RecoveryTask,
688
+ parse_obj_as(
689
+ type_=RecoveryTask, # type: ignore
690
+ object_=_response.json(),
691
+ ),
692
+ )
693
+ return HttpResponse(response=_response, data=_data)
694
+ if _response.status_code == 404:
695
+ raise NotFoundError(
696
+ headers=dict(_response.headers),
697
+ body=typing.cast(
698
+ NotFoundErrorResponse,
699
+ parse_obj_as(
700
+ type_=NotFoundErrorResponse, # type: ignore
701
+ object_=_response.json(),
702
+ ),
703
+ ),
704
+ )
705
+ if _response.status_code == 422:
706
+ raise UnprocessableEntityError(
707
+ headers=dict(_response.headers),
708
+ body=typing.cast(
709
+ typing.Optional[typing.Any],
710
+ parse_obj_as(
711
+ type_=typing.Optional[typing.Any], # type: ignore
712
+ object_=_response.json(),
713
+ ),
714
+ ),
715
+ )
716
+ if _response.status_code == 429:
717
+ raise TooManyRequestsError(
718
+ headers=dict(_response.headers),
719
+ body=typing.cast(
720
+ RateLimitErrorResponse,
721
+ parse_obj_as(
722
+ type_=RateLimitErrorResponse, # type: ignore
723
+ object_=_response.json(),
724
+ ),
725
+ ),
726
+ )
727
+ _response_json = _response.json()
728
+ except JSONDecodeError:
729
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
730
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
731
+
732
+
733
+ class AsyncRawEventsClient:
734
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
735
+ self._client_wrapper = client_wrapper
736
+
737
+ async def get_messages(
738
+ self,
739
+ *,
740
+ event_types: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
741
+ request_options: typing.Optional[RequestOptions] = None,
742
+ ) -> AsyncHttpResponse[typing.List[EventMessage]]:
743
+ """
744
+ Retrieve all event messages for your organization.
745
+
746
+ Event messages represent webhook payloads that were sent (or attempted to be sent)
747
+ to your subscribed endpoints. Each message contains the event type, payload data,
748
+ and delivery status information.
749
+
750
+ Use the `event_types` query parameter to filter messages by specific event types,
751
+ such as `sync.completed` or `sync.failed`.
752
+
753
+ Parameters
754
+ ----------
755
+ event_types : typing.Optional[typing.Union[str, typing.Sequence[str]]]
756
+ Filter messages by event type(s). Accepts multiple values, e.g., `?event_types=sync.completed&event_types=sync.failed`.
757
+
758
+ request_options : typing.Optional[RequestOptions]
759
+ Request-specific configuration.
760
+
761
+ Returns
762
+ -------
763
+ AsyncHttpResponse[typing.List[EventMessage]]
764
+ List of event messages
765
+ """
766
+ _response = await self._client_wrapper.httpx_client.request(
767
+ "events/messages",
768
+ method="GET",
769
+ params={
770
+ "event_types": event_types,
771
+ },
772
+ request_options=request_options,
773
+ )
774
+ try:
775
+ if 200 <= _response.status_code < 300:
776
+ _data = typing.cast(
777
+ typing.List[EventMessage],
778
+ parse_obj_as(
779
+ type_=typing.List[EventMessage], # type: ignore
780
+ object_=_response.json(),
781
+ ),
782
+ )
783
+ return AsyncHttpResponse(response=_response, data=_data)
784
+ if _response.status_code == 422:
785
+ raise UnprocessableEntityError(
786
+ headers=dict(_response.headers),
787
+ body=typing.cast(
788
+ typing.Optional[typing.Any],
789
+ parse_obj_as(
790
+ type_=typing.Optional[typing.Any], # type: ignore
791
+ object_=_response.json(),
792
+ ),
793
+ ),
794
+ )
795
+ if _response.status_code == 429:
796
+ raise TooManyRequestsError(
797
+ headers=dict(_response.headers),
798
+ body=typing.cast(
799
+ RateLimitErrorResponse,
800
+ parse_obj_as(
801
+ type_=RateLimitErrorResponse, # type: ignore
802
+ object_=_response.json(),
803
+ ),
804
+ ),
805
+ )
806
+ _response_json = _response.json()
807
+ except JSONDecodeError:
808
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
809
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
810
+
811
+ async def get_message(
812
+ self,
813
+ message_id: str,
814
+ *,
815
+ include_attempts: typing.Optional[bool] = None,
816
+ request_options: typing.Optional[RequestOptions] = None,
817
+ ) -> AsyncHttpResponse[EventMessageWithAttempts]:
818
+ """
819
+ Retrieve a specific event message by its ID.
820
+
821
+ Returns the full message details including the event type, payload data,
822
+ timestamp, and delivery channel information. Use this to inspect the
823
+ exact payload that was sent to your webhook endpoints.
824
+
825
+ Use `include_attempts=true` to also retrieve delivery attempts for this message,
826
+ which include HTTP response codes, response bodies, and timestamps for debugging
827
+ delivery failures.
828
+
829
+ Parameters
830
+ ----------
831
+ message_id : str
832
+ The unique identifier of the message to retrieve (UUID).
833
+
834
+ include_attempts : typing.Optional[bool]
835
+ Include delivery attempts for this message. Each attempt includes the HTTP response code, response body, and timestamp.
836
+
837
+ request_options : typing.Optional[RequestOptions]
838
+ Request-specific configuration.
839
+
840
+ Returns
841
+ -------
842
+ AsyncHttpResponse[EventMessageWithAttempts]
843
+ Event message details
844
+ """
845
+ _response = await self._client_wrapper.httpx_client.request(
846
+ f"events/messages/{jsonable_encoder(message_id)}",
847
+ method="GET",
848
+ params={
849
+ "include_attempts": include_attempts,
850
+ },
851
+ request_options=request_options,
852
+ )
853
+ try:
854
+ if 200 <= _response.status_code < 300:
855
+ _data = typing.cast(
856
+ EventMessageWithAttempts,
857
+ parse_obj_as(
858
+ type_=EventMessageWithAttempts, # type: ignore
859
+ object_=_response.json(),
860
+ ),
861
+ )
862
+ return AsyncHttpResponse(response=_response, data=_data)
863
+ if _response.status_code == 404:
864
+ raise NotFoundError(
865
+ headers=dict(_response.headers),
866
+ body=typing.cast(
867
+ NotFoundErrorResponse,
868
+ parse_obj_as(
869
+ type_=NotFoundErrorResponse, # type: ignore
870
+ object_=_response.json(),
871
+ ),
872
+ ),
873
+ )
874
+ if _response.status_code == 422:
875
+ raise UnprocessableEntityError(
876
+ headers=dict(_response.headers),
877
+ body=typing.cast(
878
+ typing.Optional[typing.Any],
879
+ parse_obj_as(
880
+ type_=typing.Optional[typing.Any], # type: ignore
881
+ object_=_response.json(),
882
+ ),
883
+ ),
884
+ )
885
+ if _response.status_code == 429:
886
+ raise TooManyRequestsError(
887
+ headers=dict(_response.headers),
888
+ body=typing.cast(
889
+ RateLimitErrorResponse,
890
+ parse_obj_as(
891
+ type_=RateLimitErrorResponse, # type: ignore
892
+ object_=_response.json(),
893
+ ),
894
+ ),
895
+ )
896
+ _response_json = _response.json()
897
+ except JSONDecodeError:
898
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
899
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
900
+
901
+ async def get_subscriptions(
902
+ self, *, request_options: typing.Optional[RequestOptions] = None
903
+ ) -> AsyncHttpResponse[typing.List[WebhookSubscription]]:
904
+ """
905
+ List all webhook subscriptions for your organization.
906
+
907
+ Returns all configured webhook endpoints, including their URLs, subscribed
908
+ event types, and current status (enabled/disabled). Use this to audit
909
+ your webhook configuration or find a specific subscription.
910
+
911
+ Parameters
912
+ ----------
913
+ request_options : typing.Optional[RequestOptions]
914
+ Request-specific configuration.
915
+
916
+ Returns
917
+ -------
918
+ AsyncHttpResponse[typing.List[WebhookSubscription]]
919
+ List of webhook subscriptions
920
+ """
921
+ _response = await self._client_wrapper.httpx_client.request(
922
+ "events/subscriptions",
923
+ method="GET",
924
+ request_options=request_options,
925
+ )
926
+ try:
927
+ if 200 <= _response.status_code < 300:
928
+ _data = typing.cast(
929
+ typing.List[WebhookSubscription],
930
+ parse_obj_as(
931
+ type_=typing.List[WebhookSubscription], # type: ignore
932
+ object_=_response.json(),
933
+ ),
934
+ )
935
+ return AsyncHttpResponse(response=_response, data=_data)
936
+ if _response.status_code == 422:
937
+ raise UnprocessableEntityError(
938
+ headers=dict(_response.headers),
939
+ body=typing.cast(
940
+ typing.Optional[typing.Any],
941
+ parse_obj_as(
942
+ type_=typing.Optional[typing.Any], # type: ignore
943
+ object_=_response.json(),
944
+ ),
945
+ ),
946
+ )
947
+ if _response.status_code == 429:
948
+ raise TooManyRequestsError(
949
+ headers=dict(_response.headers),
950
+ body=typing.cast(
951
+ RateLimitErrorResponse,
952
+ parse_obj_as(
953
+ type_=RateLimitErrorResponse, # type: ignore
954
+ object_=_response.json(),
955
+ ),
956
+ ),
957
+ )
958
+ _response_json = _response.json()
959
+ except JSONDecodeError:
960
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
961
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
962
+
963
+ async def create_subscription(
964
+ self,
965
+ *,
966
+ url: str,
967
+ event_types: typing.Sequence[EventType],
968
+ secret: typing.Optional[str] = OMIT,
969
+ request_options: typing.Optional[RequestOptions] = None,
970
+ ) -> AsyncHttpResponse[WebhookSubscription]:
971
+ """
972
+ Create a new webhook subscription.
973
+
974
+ Webhook subscriptions allow you to receive real-time notifications when events
975
+ occur in Airweave. When you create a subscription, you specify:
976
+
977
+ - **URL**: The HTTPS endpoint where events will be delivered
978
+ - **Event Types**: Which events you want to receive (e.g., `sync.completed`, `sync.failed`)
979
+ - **Secret** (optional): A custom signing secret for verifying webhook signatures
980
+
981
+ After creation, Airweave will send HTTP POST requests to your URL whenever
982
+ matching events occur. Each request includes a signature header for verification.
983
+
984
+ Parameters
985
+ ----------
986
+ url : str
987
+ The HTTPS URL where webhook events will be delivered. Must be a publicly accessible endpoint that returns a 2xx status code.
988
+
989
+ event_types : typing.Sequence[EventType]
990
+ List of event types to subscribe to. Events not in this list will not be delivered to this subscription. Available types: `sync.pending`, `sync.running`, `sync.completed`, `sync.failed`, `sync.cancelled`.
991
+
992
+ secret : typing.Optional[str]
993
+ Optional custom signing secret for webhook signature verification. If not provided, a secure secret will be auto-generated. Must be at least 24 characters if specified.
994
+
995
+ request_options : typing.Optional[RequestOptions]
996
+ Request-specific configuration.
997
+
998
+ Returns
999
+ -------
1000
+ AsyncHttpResponse[WebhookSubscription]
1001
+ Created subscription
1002
+ """
1003
+ _response = await self._client_wrapper.httpx_client.request(
1004
+ "events/subscriptions",
1005
+ method="POST",
1006
+ json={
1007
+ "url": url,
1008
+ "event_types": event_types,
1009
+ "secret": secret,
1010
+ },
1011
+ headers={
1012
+ "content-type": "application/json",
1013
+ },
1014
+ request_options=request_options,
1015
+ omit=OMIT,
1016
+ )
1017
+ try:
1018
+ if 200 <= _response.status_code < 300:
1019
+ _data = typing.cast(
1020
+ WebhookSubscription,
1021
+ parse_obj_as(
1022
+ type_=WebhookSubscription, # type: ignore
1023
+ object_=_response.json(),
1024
+ ),
1025
+ )
1026
+ return AsyncHttpResponse(response=_response, data=_data)
1027
+ if _response.status_code == 422:
1028
+ raise UnprocessableEntityError(
1029
+ headers=dict(_response.headers),
1030
+ body=typing.cast(
1031
+ typing.Optional[typing.Any],
1032
+ parse_obj_as(
1033
+ type_=typing.Optional[typing.Any], # type: ignore
1034
+ object_=_response.json(),
1035
+ ),
1036
+ ),
1037
+ )
1038
+ if _response.status_code == 429:
1039
+ raise TooManyRequestsError(
1040
+ headers=dict(_response.headers),
1041
+ body=typing.cast(
1042
+ RateLimitErrorResponse,
1043
+ parse_obj_as(
1044
+ type_=RateLimitErrorResponse, # type: ignore
1045
+ object_=_response.json(),
1046
+ ),
1047
+ ),
1048
+ )
1049
+ _response_json = _response.json()
1050
+ except JSONDecodeError:
1051
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1052
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1053
+
1054
+ async def get_subscription(
1055
+ self,
1056
+ subscription_id: str,
1057
+ *,
1058
+ include_secret: typing.Optional[bool] = None,
1059
+ request_options: typing.Optional[RequestOptions] = None,
1060
+ ) -> AsyncHttpResponse[WebhookSubscription]:
1061
+ """
1062
+ Retrieve a specific webhook subscription with its recent delivery attempts.
1063
+
1064
+ Returns the subscription configuration along with a history of message delivery
1065
+ attempts. This is useful for debugging delivery issues or verifying that your
1066
+ endpoint is correctly receiving events.
1067
+
1068
+ Use `include_secret=true` to also retrieve the signing secret for webhook
1069
+ signature verification. Keep this secret secure.
1070
+
1071
+ Parameters
1072
+ ----------
1073
+ subscription_id : str
1074
+ The unique identifier of the subscription to retrieve (UUID).
1075
+
1076
+ include_secret : typing.Optional[bool]
1077
+ Include the signing secret for webhook signature verification. Keep this secret secure and use it to verify the 'svix-signature' header.
1078
+
1079
+ request_options : typing.Optional[RequestOptions]
1080
+ Request-specific configuration.
1081
+
1082
+ Returns
1083
+ -------
1084
+ AsyncHttpResponse[WebhookSubscription]
1085
+ Subscription with delivery attempts
1086
+ """
1087
+ _response = await self._client_wrapper.httpx_client.request(
1088
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
1089
+ method="GET",
1090
+ params={
1091
+ "include_secret": include_secret,
1092
+ },
1093
+ request_options=request_options,
1094
+ )
1095
+ try:
1096
+ if 200 <= _response.status_code < 300:
1097
+ _data = typing.cast(
1098
+ WebhookSubscription,
1099
+ parse_obj_as(
1100
+ type_=WebhookSubscription, # type: ignore
1101
+ object_=_response.json(),
1102
+ ),
1103
+ )
1104
+ return AsyncHttpResponse(response=_response, data=_data)
1105
+ if _response.status_code == 404:
1106
+ raise NotFoundError(
1107
+ headers=dict(_response.headers),
1108
+ body=typing.cast(
1109
+ NotFoundErrorResponse,
1110
+ parse_obj_as(
1111
+ type_=NotFoundErrorResponse, # type: ignore
1112
+ object_=_response.json(),
1113
+ ),
1114
+ ),
1115
+ )
1116
+ if _response.status_code == 422:
1117
+ raise UnprocessableEntityError(
1118
+ headers=dict(_response.headers),
1119
+ body=typing.cast(
1120
+ typing.Optional[typing.Any],
1121
+ parse_obj_as(
1122
+ type_=typing.Optional[typing.Any], # type: ignore
1123
+ object_=_response.json(),
1124
+ ),
1125
+ ),
1126
+ )
1127
+ if _response.status_code == 429:
1128
+ raise TooManyRequestsError(
1129
+ headers=dict(_response.headers),
1130
+ body=typing.cast(
1131
+ RateLimitErrorResponse,
1132
+ parse_obj_as(
1133
+ type_=RateLimitErrorResponse, # type: ignore
1134
+ object_=_response.json(),
1135
+ ),
1136
+ ),
1137
+ )
1138
+ _response_json = _response.json()
1139
+ except JSONDecodeError:
1140
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1141
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1142
+
1143
+ async def delete_subscription(
1144
+ self, subscription_id: str, *, request_options: typing.Optional[RequestOptions] = None
1145
+ ) -> AsyncHttpResponse[WebhookSubscription]:
1146
+ """
1147
+ Permanently delete a webhook subscription.
1148
+
1149
+ Once deleted, Airweave will stop sending events to this endpoint immediately.
1150
+ This action cannot be undone. Any pending message deliveries will be cancelled.
1151
+
1152
+ If you want to temporarily stop receiving events, consider disabling the
1153
+ subscription instead using the PATCH endpoint.
1154
+
1155
+ Parameters
1156
+ ----------
1157
+ subscription_id : str
1158
+ The unique identifier of the subscription to delete (UUID).
1159
+
1160
+ request_options : typing.Optional[RequestOptions]
1161
+ Request-specific configuration.
1162
+
1163
+ Returns
1164
+ -------
1165
+ AsyncHttpResponse[WebhookSubscription]
1166
+ Deleted subscription
1167
+ """
1168
+ _response = await self._client_wrapper.httpx_client.request(
1169
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
1170
+ method="DELETE",
1171
+ request_options=request_options,
1172
+ )
1173
+ try:
1174
+ if 200 <= _response.status_code < 300:
1175
+ _data = typing.cast(
1176
+ WebhookSubscription,
1177
+ parse_obj_as(
1178
+ type_=WebhookSubscription, # type: ignore
1179
+ object_=_response.json(),
1180
+ ),
1181
+ )
1182
+ return AsyncHttpResponse(response=_response, data=_data)
1183
+ if _response.status_code == 404:
1184
+ raise NotFoundError(
1185
+ headers=dict(_response.headers),
1186
+ body=typing.cast(
1187
+ NotFoundErrorResponse,
1188
+ parse_obj_as(
1189
+ type_=NotFoundErrorResponse, # type: ignore
1190
+ object_=_response.json(),
1191
+ ),
1192
+ ),
1193
+ )
1194
+ if _response.status_code == 422:
1195
+ raise UnprocessableEntityError(
1196
+ headers=dict(_response.headers),
1197
+ body=typing.cast(
1198
+ typing.Optional[typing.Any],
1199
+ parse_obj_as(
1200
+ type_=typing.Optional[typing.Any], # type: ignore
1201
+ object_=_response.json(),
1202
+ ),
1203
+ ),
1204
+ )
1205
+ if _response.status_code == 429:
1206
+ raise TooManyRequestsError(
1207
+ headers=dict(_response.headers),
1208
+ body=typing.cast(
1209
+ RateLimitErrorResponse,
1210
+ parse_obj_as(
1211
+ type_=RateLimitErrorResponse, # type: ignore
1212
+ object_=_response.json(),
1213
+ ),
1214
+ ),
1215
+ )
1216
+ _response_json = _response.json()
1217
+ except JSONDecodeError:
1218
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1219
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1220
+
1221
+ async def patch_subscription(
1222
+ self,
1223
+ subscription_id: str,
1224
+ *,
1225
+ url: typing.Optional[str] = OMIT,
1226
+ event_types: typing.Optional[typing.Sequence[EventType]] = OMIT,
1227
+ disabled: typing.Optional[bool] = OMIT,
1228
+ recover_since: typing.Optional[dt.datetime] = OMIT,
1229
+ request_options: typing.Optional[RequestOptions] = None,
1230
+ ) -> AsyncHttpResponse[WebhookSubscription]:
1231
+ """
1232
+ Update an existing webhook subscription.
1233
+
1234
+ Use this endpoint to modify a subscription's configuration. You can:
1235
+
1236
+ - **Change the URL**: Update where events are delivered
1237
+ - **Update event types**: Modify which events trigger notifications
1238
+ - **Enable/disable**: Temporarily pause delivery without deleting the subscription
1239
+ - **Recover messages**: When re-enabling, optionally recover missed messages
1240
+
1241
+ Only include the fields you want to change. Omitted fields will retain their
1242
+ current values.
1243
+
1244
+ When re-enabling a subscription (`disabled: false`), you can optionally provide
1245
+ `recover_since` to automatically retry all messages that were generated while
1246
+ the subscription was disabled.
1247
+
1248
+ Parameters
1249
+ ----------
1250
+ subscription_id : str
1251
+ The unique identifier of the subscription to update (UUID).
1252
+
1253
+ url : typing.Optional[str]
1254
+ New URL for webhook delivery. Must be a publicly accessible HTTPS endpoint.
1255
+
1256
+ event_types : typing.Optional[typing.Sequence[EventType]]
1257
+ New list of event types to subscribe to. This replaces the existing list entirely.
1258
+
1259
+ disabled : typing.Optional[bool]
1260
+ Set to `true` to pause delivery to this subscription, or `false` to resume. Disabled subscriptions will not receive events.
1261
+
1262
+ recover_since : typing.Optional[dt.datetime]
1263
+ When re-enabling a subscription (`disabled: false`), optionally recover failed messages from this timestamp. Only applies when enabling.
1264
+
1265
+ request_options : typing.Optional[RequestOptions]
1266
+ Request-specific configuration.
1267
+
1268
+ Returns
1269
+ -------
1270
+ AsyncHttpResponse[WebhookSubscription]
1271
+ Updated subscription
1272
+ """
1273
+ _response = await self._client_wrapper.httpx_client.request(
1274
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}",
1275
+ method="PATCH",
1276
+ json={
1277
+ "url": url,
1278
+ "event_types": event_types,
1279
+ "disabled": disabled,
1280
+ "recover_since": recover_since,
1281
+ },
1282
+ headers={
1283
+ "content-type": "application/json",
1284
+ },
1285
+ request_options=request_options,
1286
+ omit=OMIT,
1287
+ )
1288
+ try:
1289
+ if 200 <= _response.status_code < 300:
1290
+ _data = typing.cast(
1291
+ WebhookSubscription,
1292
+ parse_obj_as(
1293
+ type_=WebhookSubscription, # type: ignore
1294
+ object_=_response.json(),
1295
+ ),
1296
+ )
1297
+ return AsyncHttpResponse(response=_response, data=_data)
1298
+ if _response.status_code == 404:
1299
+ raise NotFoundError(
1300
+ headers=dict(_response.headers),
1301
+ body=typing.cast(
1302
+ NotFoundErrorResponse,
1303
+ parse_obj_as(
1304
+ type_=NotFoundErrorResponse, # type: ignore
1305
+ object_=_response.json(),
1306
+ ),
1307
+ ),
1308
+ )
1309
+ if _response.status_code == 422:
1310
+ raise UnprocessableEntityError(
1311
+ headers=dict(_response.headers),
1312
+ body=typing.cast(
1313
+ typing.Optional[typing.Any],
1314
+ parse_obj_as(
1315
+ type_=typing.Optional[typing.Any], # type: ignore
1316
+ object_=_response.json(),
1317
+ ),
1318
+ ),
1319
+ )
1320
+ if _response.status_code == 429:
1321
+ raise TooManyRequestsError(
1322
+ headers=dict(_response.headers),
1323
+ body=typing.cast(
1324
+ RateLimitErrorResponse,
1325
+ parse_obj_as(
1326
+ type_=RateLimitErrorResponse, # type: ignore
1327
+ object_=_response.json(),
1328
+ ),
1329
+ ),
1330
+ )
1331
+ _response_json = _response.json()
1332
+ except JSONDecodeError:
1333
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1334
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1335
+
1336
+ async def recover_failed_messages(
1337
+ self,
1338
+ subscription_id: str,
1339
+ *,
1340
+ since: dt.datetime,
1341
+ until: typing.Optional[dt.datetime] = OMIT,
1342
+ request_options: typing.Optional[RequestOptions] = None,
1343
+ ) -> AsyncHttpResponse[RecoveryTask]:
1344
+ """
1345
+ Retry failed message deliveries for a webhook subscription.
1346
+
1347
+ Triggers a recovery process that replays all failed messages within the
1348
+ specified time window. This is useful when:
1349
+
1350
+ - Your endpoint was temporarily down and you want to catch up
1351
+ - You've fixed a bug in your webhook handler
1352
+ - You want to reprocess events after re-enabling a disabled subscription
1353
+
1354
+ Messages are retried in chronological order. Successfully delivered messages
1355
+ are skipped; only failed or pending messages are retried.
1356
+
1357
+ Parameters
1358
+ ----------
1359
+ subscription_id : str
1360
+ The unique identifier of the subscription to recover messages for (UUID).
1361
+
1362
+ since : dt.datetime
1363
+ Start of the recovery time window (inclusive). All failed messages from this time onward will be retried.
1364
+
1365
+ until : typing.Optional[dt.datetime]
1366
+ End of the recovery time window (exclusive). If not specified, recovers all failed messages up to now.
1367
+
1368
+ request_options : typing.Optional[RequestOptions]
1369
+ Request-specific configuration.
1370
+
1371
+ Returns
1372
+ -------
1373
+ AsyncHttpResponse[RecoveryTask]
1374
+ Recovery task information
1375
+ """
1376
+ _response = await self._client_wrapper.httpx_client.request(
1377
+ f"events/subscriptions/{jsonable_encoder(subscription_id)}/recover",
1378
+ method="POST",
1379
+ json={
1380
+ "since": since,
1381
+ "until": until,
1382
+ },
1383
+ headers={
1384
+ "content-type": "application/json",
1385
+ },
1386
+ request_options=request_options,
1387
+ omit=OMIT,
1388
+ )
1389
+ try:
1390
+ if 200 <= _response.status_code < 300:
1391
+ _data = typing.cast(
1392
+ RecoveryTask,
1393
+ parse_obj_as(
1394
+ type_=RecoveryTask, # type: ignore
1395
+ object_=_response.json(),
1396
+ ),
1397
+ )
1398
+ return AsyncHttpResponse(response=_response, data=_data)
1399
+ if _response.status_code == 404:
1400
+ raise NotFoundError(
1401
+ headers=dict(_response.headers),
1402
+ body=typing.cast(
1403
+ NotFoundErrorResponse,
1404
+ parse_obj_as(
1405
+ type_=NotFoundErrorResponse, # type: ignore
1406
+ object_=_response.json(),
1407
+ ),
1408
+ ),
1409
+ )
1410
+ if _response.status_code == 422:
1411
+ raise UnprocessableEntityError(
1412
+ headers=dict(_response.headers),
1413
+ body=typing.cast(
1414
+ typing.Optional[typing.Any],
1415
+ parse_obj_as(
1416
+ type_=typing.Optional[typing.Any], # type: ignore
1417
+ object_=_response.json(),
1418
+ ),
1419
+ ),
1420
+ )
1421
+ if _response.status_code == 429:
1422
+ raise TooManyRequestsError(
1423
+ headers=dict(_response.headers),
1424
+ body=typing.cast(
1425
+ RateLimitErrorResponse,
1426
+ parse_obj_as(
1427
+ type_=RateLimitErrorResponse, # type: ignore
1428
+ object_=_response.json(),
1429
+ ),
1430
+ ),
1431
+ )
1432
+ _response_json = _response.json()
1433
+ except JSONDecodeError:
1434
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1435
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)