agentspay 0.1.0 → 0.2.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.
Files changed (126) hide show
  1. package/.env.example +40 -0
  2. package/README.md +35 -4
  3. package/VERIFICATION_SUMMARY.txt +151 -0
  4. package/dist/api/server.d.ts.map +1 -1
  5. package/dist/api/server.js +530 -74
  6. package/dist/api/server.js.map +1 -1
  7. package/dist/bsv/crypto.d.ts +65 -0
  8. package/dist/bsv/crypto.d.ts.map +1 -0
  9. package/dist/bsv/crypto.js +158 -0
  10. package/dist/bsv/crypto.js.map +1 -0
  11. package/dist/bsv/mnee.d.ts +88 -0
  12. package/dist/bsv/mnee.d.ts.map +1 -0
  13. package/dist/bsv/mnee.js +173 -0
  14. package/dist/bsv/mnee.js.map +1 -0
  15. package/dist/bsv/opreturn.d.ts +22 -0
  16. package/dist/bsv/opreturn.d.ts.map +1 -0
  17. package/dist/bsv/opreturn.js +117 -0
  18. package/dist/bsv/opreturn.js.map +1 -0
  19. package/dist/bsv/whatsonchain.d.ts +46 -0
  20. package/dist/bsv/whatsonchain.d.ts.map +1 -0
  21. package/dist/bsv/whatsonchain.js +98 -0
  22. package/dist/bsv/whatsonchain.js.map +1 -0
  23. package/dist/config.d.ts +38 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +85 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/currency/currency.d.ts +70 -0
  28. package/dist/currency/currency.d.ts.map +1 -0
  29. package/dist/currency/currency.js +137 -0
  30. package/dist/currency/currency.js.map +1 -0
  31. package/dist/disputes/dispute.d.ts +50 -0
  32. package/dist/disputes/dispute.d.ts.map +1 -0
  33. package/dist/disputes/dispute.js +162 -0
  34. package/dist/disputes/dispute.js.map +1 -0
  35. package/dist/docs/openapi.yaml +1079 -0
  36. package/dist/docs/swagger.d.ts +12 -0
  37. package/dist/docs/swagger.d.ts.map +1 -0
  38. package/dist/docs/swagger.js +104 -0
  39. package/dist/docs/swagger.js.map +1 -0
  40. package/dist/middleware/auth.d.ts +20 -0
  41. package/dist/middleware/auth.d.ts.map +1 -0
  42. package/dist/middleware/auth.js +32 -0
  43. package/dist/middleware/auth.js.map +1 -0
  44. package/dist/middleware/rateLimit.d.ts +25 -0
  45. package/dist/middleware/rateLimit.d.ts.map +1 -0
  46. package/dist/middleware/rateLimit.js +61 -0
  47. package/dist/middleware/rateLimit.js.map +1 -0
  48. package/dist/payment/payment.d.ts +79 -9
  49. package/dist/payment/payment.d.ts.map +1 -1
  50. package/dist/payment/payment.js +387 -47
  51. package/dist/payment/payment.js.map +1 -1
  52. package/dist/registry/db.d.ts.map +1 -1
  53. package/dist/registry/db.js +110 -3
  54. package/dist/registry/db.js.map +1 -1
  55. package/dist/registry/registry.d.ts +1 -1
  56. package/dist/registry/registry.d.ts.map +1 -1
  57. package/dist/registry/registry.js +12 -4
  58. package/dist/registry/registry.js.map +1 -1
  59. package/dist/types/index.d.ts +34 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/types/index.js.map +1 -1
  62. package/dist/utils/validation.d.ts +27 -0
  63. package/dist/utils/validation.d.ts.map +1 -0
  64. package/dist/utils/validation.js +164 -0
  65. package/dist/utils/validation.js.map +1 -0
  66. package/dist/verification/receipt.d.ts +42 -0
  67. package/dist/verification/receipt.d.ts.map +1 -0
  68. package/dist/verification/receipt.js +10 -0
  69. package/dist/verification/receipt.js.map +1 -0
  70. package/dist/verification/verification.d.ts +41 -0
  71. package/dist/verification/verification.d.ts.map +1 -0
  72. package/dist/verification/verification.js +217 -0
  73. package/dist/verification/verification.js.map +1 -0
  74. package/dist/wallet/providerManager.d.ts +32 -0
  75. package/dist/wallet/providerManager.d.ts.map +1 -0
  76. package/dist/wallet/providerManager.js +118 -0
  77. package/dist/wallet/providerManager.js.map +1 -0
  78. package/dist/wallet/providers/handcash.d.ts +22 -0
  79. package/dist/wallet/providers/handcash.d.ts.map +1 -0
  80. package/dist/wallet/providers/handcash.js +214 -0
  81. package/dist/wallet/providers/handcash.js.map +1 -0
  82. package/dist/wallet/providers/internal.d.ts +15 -0
  83. package/dist/wallet/providers/internal.d.ts.map +1 -0
  84. package/dist/wallet/providers/internal.js +208 -0
  85. package/dist/wallet/providers/internal.js.map +1 -0
  86. package/dist/wallet/providers/types.d.ts +50 -0
  87. package/dist/wallet/providers/types.d.ts.map +1 -0
  88. package/dist/wallet/providers/types.js +6 -0
  89. package/dist/wallet/providers/types.js.map +1 -0
  90. package/dist/wallet/providers/yours.d.ts +18 -0
  91. package/dist/wallet/providers/yours.d.ts.map +1 -0
  92. package/dist/wallet/providers/yours.js +122 -0
  93. package/dist/wallet/providers/yours.js.map +1 -0
  94. package/dist/wallet/wallet.d.ts +52 -5
  95. package/dist/wallet/wallet.d.ts.map +1 -1
  96. package/dist/wallet/wallet.js +223 -34
  97. package/dist/wallet/wallet.js.map +1 -1
  98. package/dist/webhooks/delivery.d.ts +37 -0
  99. package/dist/webhooks/delivery.d.ts.map +1 -0
  100. package/dist/webhooks/delivery.js +182 -0
  101. package/dist/webhooks/delivery.js.map +1 -0
  102. package/dist/webhooks/webhook.d.ts +85 -0
  103. package/dist/webhooks/webhook.d.ts.map +1 -0
  104. package/dist/webhooks/webhook.js +271 -0
  105. package/dist/webhooks/webhook.js.map +1 -0
  106. package/package.json +74 -54
  107. package/sdk-python/LICENSE +21 -0
  108. package/sdk-python/MANIFEST.in +9 -0
  109. package/sdk-python/README.md +372 -0
  110. package/sdk-python/agentspay/__init__.py +97 -0
  111. package/sdk-python/agentspay/client.py +256 -0
  112. package/sdk-python/agentspay/disputes.py +174 -0
  113. package/sdk-python/agentspay/exceptions.py +53 -0
  114. package/sdk-python/agentspay/payments.py +169 -0
  115. package/sdk-python/agentspay/services.py +198 -0
  116. package/sdk-python/agentspay/types.py +154 -0
  117. package/sdk-python/agentspay/wallet.py +113 -0
  118. package/sdk-python/agentspay/webhooks.py +195 -0
  119. package/sdk-python/examples/consumer.py +147 -0
  120. package/sdk-python/examples/provider.py +116 -0
  121. package/sdk-python/pyproject.toml +61 -0
  122. package/sdk-python/setup.py +53 -0
  123. package/sdk-python/tests/test_client.py +221 -0
  124. package/test-addr.js +29 -0
  125. package/test-mnee-simple.js +51 -0
  126. package/test-mnee.js +47 -0
@@ -0,0 +1,198 @@
1
+ """Service operations for AgentPay SDK"""
2
+
3
+ from typing import Optional, List
4
+ import requests
5
+ from .types import Service, ServiceQuery
6
+ from .exceptions import ServiceError
7
+
8
+
9
+ class ServiceOperations:
10
+ """Handles service registration and discovery"""
11
+
12
+ def __init__(self, base_url: str, api_key: Optional[str] = None):
13
+ self.base_url = base_url
14
+ self.api_key = api_key
15
+
16
+ def _get_headers(self) -> dict:
17
+ """Get request headers with API key if available"""
18
+ headers = {"Content-Type": "application/json"}
19
+ if self.api_key:
20
+ headers["Authorization"] = f"Bearer {self.api_key}"
21
+ return headers
22
+
23
+ def register_service(
24
+ self,
25
+ agent_id: str,
26
+ name: str,
27
+ description: str,
28
+ price: int,
29
+ endpoint: str,
30
+ category: str = "general",
31
+ currency: str = "BSV",
32
+ method: str = "POST",
33
+ timeout: int = 30,
34
+ dispute_window: int = 30,
35
+ input_schema: Optional[dict] = None,
36
+ output_schema: Optional[dict] = None
37
+ ) -> Service:
38
+ """
39
+ Register a new service
40
+
41
+ Args:
42
+ agent_id: Provider wallet ID
43
+ name: Service name
44
+ description: Service description
45
+ price: Price per execution (satoshis for BSV, cents for MNEE)
46
+ endpoint: Service endpoint URL
47
+ category: Service category
48
+ currency: "BSV" or "MNEE"
49
+ method: HTTP method ("POST" or "GET")
50
+ timeout: Max execution time in seconds
51
+ dispute_window: Dispute window in minutes
52
+ input_schema: Optional JSON schema for input validation
53
+ output_schema: Optional JSON schema for output validation
54
+
55
+ Returns:
56
+ Service: The registered service
57
+
58
+ Raises:
59
+ ServiceError: If registration fails
60
+ """
61
+ payload = {
62
+ "agentId": agent_id,
63
+ "name": name,
64
+ "description": description,
65
+ "category": category,
66
+ "price": price,
67
+ "currency": currency.upper(),
68
+ "endpoint": endpoint,
69
+ "method": method.upper(),
70
+ "timeout": timeout,
71
+ "disputeWindow": dispute_window
72
+ }
73
+
74
+ if input_schema:
75
+ payload["inputSchema"] = input_schema
76
+ if output_schema:
77
+ payload["outputSchema"] = output_schema
78
+
79
+ try:
80
+ response = requests.post(
81
+ f"{self.base_url}/api/services",
82
+ json=payload,
83
+ headers=self._get_headers()
84
+ )
85
+ response.raise_for_status()
86
+ data = response.json()
87
+
88
+ service_data = data.get("service")
89
+ if not service_data:
90
+ raise ServiceError("Invalid response: missing service data")
91
+
92
+ return self._parse_service(service_data)
93
+ except requests.RequestException as e:
94
+ raise ServiceError(f"Failed to register service: {str(e)}") from e
95
+
96
+ def search_services(
97
+ self,
98
+ keyword: Optional[str] = None,
99
+ category: Optional[str] = None,
100
+ max_price: Optional[int] = None,
101
+ min_rating: Optional[float] = None,
102
+ limit: int = 20,
103
+ offset: int = 0
104
+ ) -> List[Service]:
105
+ """
106
+ Search for services
107
+
108
+ Args:
109
+ keyword: Search keyword
110
+ category: Filter by category
111
+ max_price: Maximum price filter
112
+ min_rating: Minimum rating filter
113
+ limit: Max results to return
114
+ offset: Result offset for pagination
115
+
116
+ Returns:
117
+ List[Service]: List of matching services
118
+
119
+ Raises:
120
+ ServiceError: If search fails
121
+ """
122
+ params = {}
123
+ if keyword:
124
+ params["q"] = keyword
125
+ if category:
126
+ params["category"] = category
127
+ if max_price is not None:
128
+ params["maxPrice"] = str(max_price)
129
+ if min_rating is not None:
130
+ params["minRating"] = str(min_rating)
131
+ if limit:
132
+ params["limit"] = str(limit)
133
+ if offset:
134
+ params["offset"] = str(offset)
135
+
136
+ try:
137
+ response = requests.get(
138
+ f"{self.base_url}/api/services",
139
+ params=params,
140
+ headers=self._get_headers()
141
+ )
142
+ response.raise_for_status()
143
+ data = response.json()
144
+
145
+ services_data = data.get("services", [])
146
+ return [self._parse_service(s) for s in services_data]
147
+ except requests.RequestException as e:
148
+ raise ServiceError(f"Failed to search services: {str(e)}") from e
149
+
150
+ def get_service(self, service_id: str) -> Service:
151
+ """
152
+ Get service by ID
153
+
154
+ Args:
155
+ service_id: Service ID
156
+
157
+ Returns:
158
+ Service: The service
159
+
160
+ Raises:
161
+ ServiceError: If service not found or request fails
162
+ """
163
+ try:
164
+ response = requests.get(
165
+ f"{self.base_url}/api/services/{service_id}",
166
+ headers=self._get_headers()
167
+ )
168
+ response.raise_for_status()
169
+ data = response.json()
170
+
171
+ service_data = data.get("service")
172
+ if not service_data:
173
+ raise ServiceError(f"Service {service_id} not found")
174
+
175
+ return self._parse_service(service_data)
176
+ except requests.RequestException as e:
177
+ raise ServiceError(f"Failed to get service: {str(e)}") from e
178
+
179
+ def _parse_service(self, data: dict) -> Service:
180
+ """Parse service data from API response"""
181
+ return Service(
182
+ id=data["id"],
183
+ agent_id=data["agentId"],
184
+ name=data["name"],
185
+ description=data["description"],
186
+ category=data["category"],
187
+ price=data["price"],
188
+ currency=data["currency"],
189
+ endpoint=data["endpoint"],
190
+ method=data["method"],
191
+ active=data["active"],
192
+ timeout=data.get("timeout", 30),
193
+ dispute_window=data.get("disputeWindow", 30),
194
+ created_at=data["createdAt"],
195
+ updated_at=data["updatedAt"],
196
+ input_schema=data.get("inputSchema"),
197
+ output_schema=data.get("outputSchema")
198
+ )
@@ -0,0 +1,154 @@
1
+ """AgentPay SDK Type Definitions"""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional, Literal, Dict, Any
5
+ from datetime import datetime
6
+
7
+
8
+ Currency = Literal["BSV", "MNEE"]
9
+ PaymentStatus = Literal["pending", "escrowed", "released", "disputed", "refunded"]
10
+ DisputeStatus = Literal["open", "under_review", "resolved_refund", "resolved_release", "resolved_split", "expired"]
11
+ DisputeResolution = Literal["refund", "release", "split"]
12
+ ExecutionStatus = Literal["success", "error"]
13
+ HttpMethod = Literal["POST", "GET"]
14
+
15
+
16
+ @dataclass
17
+ class AgentWallet:
18
+ """Agent wallet representation"""
19
+ id: str
20
+ public_key: str
21
+ address: str
22
+ created_at: str
23
+ balance: Optional[int] = None # satoshis (BSV)
24
+ balance_mnee: Optional[int] = None # cents (MNEE)
25
+
26
+
27
+ @dataclass
28
+ class Service:
29
+ """Service registration"""
30
+ id: str
31
+ agent_id: str
32
+ name: str
33
+ description: str
34
+ category: str
35
+ price: int # satoshis for BSV, cents for MNEE
36
+ currency: Currency
37
+ endpoint: str
38
+ method: HttpMethod
39
+ active: bool
40
+ timeout: int # seconds
41
+ dispute_window: int # minutes
42
+ created_at: str
43
+ updated_at: str
44
+ input_schema: Optional[Dict[str, Any]] = None
45
+ output_schema: Optional[Dict[str, Any]] = None
46
+
47
+
48
+ @dataclass
49
+ class Payment:
50
+ """Payment record"""
51
+ id: str
52
+ service_id: str
53
+ buyer_wallet_id: str
54
+ seller_wallet_id: str
55
+ amount: int
56
+ platform_fee: int
57
+ currency: Currency
58
+ status: PaymentStatus
59
+ created_at: str
60
+ dispute_status: Optional[str] = None
61
+ tx_id: Optional[str] = None
62
+ completed_at: Optional[str] = None
63
+
64
+
65
+ @dataclass
66
+ class ExecutionRequest:
67
+ """Service execution request"""
68
+ service_id: str
69
+ buyer_wallet_id: str
70
+ input: Dict[str, Any]
71
+
72
+
73
+ @dataclass
74
+ class ExecutionResult:
75
+ """Service execution result"""
76
+ payment_id: str
77
+ service_id: str
78
+ output: Dict[str, Any]
79
+ execution_time_ms: int
80
+ status: ExecutionStatus
81
+ receipt: Optional['ExecutionReceipt'] = None
82
+ payment: Optional[Payment] = None
83
+
84
+
85
+ @dataclass
86
+ class ExecutionReceipt:
87
+ """Cryptographic execution receipt"""
88
+ id: str
89
+ payment_id: str
90
+ service_id: str
91
+ input_hash: str
92
+ output_hash: str
93
+ timestamp: int
94
+ execution_time_ms: int
95
+ provider_signature: str
96
+ platform_signature: str
97
+ receipt_hash: str
98
+ blockchain_tx_id: Optional[str] = None
99
+ blockchain_anchored_at: Optional[str] = None
100
+
101
+
102
+ @dataclass
103
+ class ReputationScore:
104
+ """Agent reputation metrics"""
105
+ agent_id: str
106
+ total_jobs: int
107
+ success_rate: float # 0-1
108
+ avg_response_time_ms: float
109
+ total_earned: int # satoshis
110
+ total_spent: int # satoshis
111
+ rating: float # 1-5
112
+
113
+
114
+ @dataclass
115
+ class Dispute:
116
+ """Dispute record"""
117
+ id: str
118
+ payment_id: str
119
+ buyer_wallet_id: str
120
+ provider_wallet_id: str
121
+ reason: str
122
+ status: DisputeStatus
123
+ created_at: str
124
+ evidence: Optional[str] = None
125
+ resolution: Optional[DisputeResolution] = None
126
+ split_percent: Optional[float] = None
127
+ resolved_at: Optional[str] = None
128
+
129
+
130
+ @dataclass
131
+ class Webhook:
132
+ """Webhook registration"""
133
+ id: str
134
+ url: str
135
+ events: list[str]
136
+ active: bool
137
+ created_at: str
138
+ secret: Optional[str] = None
139
+
140
+
141
+ @dataclass
142
+ class ServiceQuery:
143
+ """Service search query"""
144
+ category: Optional[str] = None
145
+ keyword: Optional[str] = None
146
+ max_price: Optional[int] = None
147
+ min_rating: Optional[float] = None
148
+ limit: Optional[int] = 20
149
+ offset: Optional[int] = 0
150
+
151
+
152
+ # Constants
153
+ PLATFORM_FEE_RATE = 0.02 # 2%
154
+ MIN_PRICE_SATOSHIS = 1
@@ -0,0 +1,113 @@
1
+ """Wallet operations for AgentPay SDK"""
2
+
3
+ from typing import Optional
4
+ import requests
5
+ from .types import AgentWallet
6
+ from .exceptions import WalletError, APIError
7
+
8
+
9
+ class WalletOperations:
10
+ """Handles wallet-related operations"""
11
+
12
+ def __init__(self, base_url: str, api_key: Optional[str] = None):
13
+ self.base_url = base_url
14
+ self.api_key = api_key
15
+
16
+ def _get_headers(self) -> dict:
17
+ """Get request headers with API key if available"""
18
+ headers = {"Content-Type": "application/json"}
19
+ if self.api_key:
20
+ headers["Authorization"] = f"Bearer {self.api_key}"
21
+ return headers
22
+
23
+ def create_wallet(self) -> AgentWallet:
24
+ """
25
+ Create a new agent wallet
26
+
27
+ Returns:
28
+ AgentWallet: The created wallet
29
+
30
+ Raises:
31
+ WalletError: If wallet creation fails
32
+ """
33
+ try:
34
+ response = requests.post(
35
+ f"{self.base_url}/api/wallets",
36
+ headers=self._get_headers()
37
+ )
38
+ response.raise_for_status()
39
+ data = response.json()
40
+
41
+ wallet_data = data.get("wallet")
42
+ if not wallet_data:
43
+ raise WalletError("Invalid response: missing wallet data")
44
+
45
+ return AgentWallet(
46
+ id=wallet_data["id"],
47
+ public_key=wallet_data["publicKey"],
48
+ address=wallet_data["address"],
49
+ created_at=wallet_data["createdAt"],
50
+ balance=wallet_data.get("balance"),
51
+ balance_mnee=wallet_data.get("balanceMnee")
52
+ )
53
+ except requests.RequestException as e:
54
+ raise WalletError(f"Failed to create wallet: {str(e)}") from e
55
+
56
+ def get_wallet(self, wallet_id: str) -> AgentWallet:
57
+ """
58
+ Get wallet by ID
59
+
60
+ Args:
61
+ wallet_id: Wallet ID
62
+
63
+ Returns:
64
+ AgentWallet: The wallet
65
+
66
+ Raises:
67
+ WalletError: If wallet not found or request fails
68
+ """
69
+ try:
70
+ response = requests.get(
71
+ f"{self.base_url}/api/wallets/{wallet_id}",
72
+ headers=self._get_headers()
73
+ )
74
+ response.raise_for_status()
75
+ data = response.json()
76
+
77
+ wallet_data = data.get("wallet")
78
+ if not wallet_data:
79
+ raise WalletError(f"Wallet {wallet_id} not found")
80
+
81
+ return AgentWallet(
82
+ id=wallet_data["id"],
83
+ public_key=wallet_data["publicKey"],
84
+ address=wallet_data["address"],
85
+ created_at=wallet_data["createdAt"],
86
+ balance=wallet_data.get("balance"),
87
+ balance_mnee=wallet_data.get("balanceMnee")
88
+ )
89
+ except requests.RequestException as e:
90
+ raise WalletError(f"Failed to get wallet: {str(e)}") from e
91
+
92
+ def get_balance(self, wallet_id: str, currency: str = "BSV") -> int:
93
+ """
94
+ Get wallet balance for a specific currency
95
+
96
+ Args:
97
+ wallet_id: Wallet ID
98
+ currency: "BSV" or "MNEE"
99
+
100
+ Returns:
101
+ int: Balance in satoshis (BSV) or cents (MNEE)
102
+
103
+ Raises:
104
+ WalletError: If balance cannot be retrieved
105
+ """
106
+ wallet = self.get_wallet(wallet_id)
107
+
108
+ if currency.upper() == "BSV":
109
+ return wallet.balance or 0
110
+ elif currency.upper() == "MNEE":
111
+ return wallet.balance_mnee or 0
112
+ else:
113
+ raise WalletError(f"Invalid currency: {currency}")
@@ -0,0 +1,195 @@
1
+ """Webhook management for AgentPay SDK"""
2
+
3
+ from typing import Optional, List
4
+ import requests
5
+ from .types import Webhook
6
+ from .exceptions import WebhookError
7
+
8
+
9
+ class WebhookOperations:
10
+ """Handles webhook registration and management"""
11
+
12
+ def __init__(self, base_url: str, api_key: Optional[str] = None):
13
+ self.base_url = base_url
14
+ self.api_key = api_key
15
+
16
+ def _get_headers(self) -> dict:
17
+ """Get request headers with API key if available"""
18
+ headers = {"Content-Type": "application/json"}
19
+ if self.api_key:
20
+ headers["Authorization"] = f"Bearer {self.api_key}"
21
+ return headers
22
+
23
+ def register_webhook(
24
+ self,
25
+ url: str,
26
+ events: List[str]
27
+ ) -> Webhook:
28
+ """
29
+ Register a new webhook
30
+
31
+ Args:
32
+ url: Webhook endpoint URL
33
+ events: List of events to subscribe to
34
+ (e.g., ["payment.completed", "payment.failed", "dispute.opened"])
35
+
36
+ Returns:
37
+ Webhook: The registered webhook
38
+
39
+ Raises:
40
+ WebhookError: If registration fails
41
+ """
42
+ payload = {
43
+ "url": url,
44
+ "events": events
45
+ }
46
+
47
+ try:
48
+ response = requests.post(
49
+ f"{self.base_url}/api/webhooks",
50
+ json=payload,
51
+ headers=self._get_headers()
52
+ )
53
+ response.raise_for_status()
54
+ data = response.json()
55
+
56
+ webhook_data = data.get("webhook")
57
+ if not webhook_data:
58
+ raise WebhookError("Invalid response: missing webhook data")
59
+
60
+ return self._parse_webhook(webhook_data)
61
+ except requests.RequestException as e:
62
+ raise WebhookError(f"Failed to register webhook: {str(e)}") from e
63
+
64
+ def get_webhook(self, webhook_id: str) -> Webhook:
65
+ """
66
+ Get webhook by ID
67
+
68
+ Args:
69
+ webhook_id: Webhook ID
70
+
71
+ Returns:
72
+ Webhook: The webhook
73
+
74
+ Raises:
75
+ WebhookError: If webhook not found or request fails
76
+ """
77
+ try:
78
+ response = requests.get(
79
+ f"{self.base_url}/api/webhooks/{webhook_id}",
80
+ headers=self._get_headers()
81
+ )
82
+ response.raise_for_status()
83
+ data = response.json()
84
+
85
+ webhook_data = data.get("webhook")
86
+ if not webhook_data:
87
+ raise WebhookError(f"Webhook {webhook_id} not found")
88
+
89
+ return self._parse_webhook(webhook_data)
90
+ except requests.RequestException as e:
91
+ raise WebhookError(f"Failed to get webhook: {str(e)}") from e
92
+
93
+ def list_webhooks(self) -> List[Webhook]:
94
+ """
95
+ List all registered webhooks
96
+
97
+ Returns:
98
+ List[Webhook]: List of webhooks
99
+
100
+ Raises:
101
+ WebhookError: If request fails
102
+ """
103
+ try:
104
+ response = requests.get(
105
+ f"{self.base_url}/api/webhooks",
106
+ headers=self._get_headers()
107
+ )
108
+ response.raise_for_status()
109
+ data = response.json()
110
+
111
+ webhooks_data = data.get("webhooks", [])
112
+ return [self._parse_webhook(w) for w in webhooks_data]
113
+ except requests.RequestException as e:
114
+ raise WebhookError(f"Failed to list webhooks: {str(e)}") from e
115
+
116
+ def delete_webhook(self, webhook_id: str) -> bool:
117
+ """
118
+ Delete a webhook
119
+
120
+ Args:
121
+ webhook_id: Webhook ID
122
+
123
+ Returns:
124
+ bool: True if deleted successfully
125
+
126
+ Raises:
127
+ WebhookError: If deletion fails
128
+ """
129
+ try:
130
+ response = requests.delete(
131
+ f"{self.base_url}/api/webhooks/{webhook_id}",
132
+ headers=self._get_headers()
133
+ )
134
+ response.raise_for_status()
135
+ return True
136
+ except requests.RequestException as e:
137
+ raise WebhookError(f"Failed to delete webhook: {str(e)}") from e
138
+
139
+ def update_webhook(
140
+ self,
141
+ webhook_id: str,
142
+ url: Optional[str] = None,
143
+ events: Optional[List[str]] = None,
144
+ active: Optional[bool] = None
145
+ ) -> Webhook:
146
+ """
147
+ Update a webhook
148
+
149
+ Args:
150
+ webhook_id: Webhook ID
151
+ url: New URL (optional)
152
+ events: New events list (optional)
153
+ active: Active status (optional)
154
+
155
+ Returns:
156
+ Webhook: Updated webhook
157
+
158
+ Raises:
159
+ WebhookError: If update fails
160
+ """
161
+ payload = {}
162
+ if url is not None:
163
+ payload["url"] = url
164
+ if events is not None:
165
+ payload["events"] = events
166
+ if active is not None:
167
+ payload["active"] = active
168
+
169
+ try:
170
+ response = requests.patch(
171
+ f"{self.base_url}/api/webhooks/{webhook_id}",
172
+ json=payload,
173
+ headers=self._get_headers()
174
+ )
175
+ response.raise_for_status()
176
+ data = response.json()
177
+
178
+ webhook_data = data.get("webhook")
179
+ if not webhook_data:
180
+ raise WebhookError("Invalid response: missing webhook data")
181
+
182
+ return self._parse_webhook(webhook_data)
183
+ except requests.RequestException as e:
184
+ raise WebhookError(f"Failed to update webhook: {str(e)}") from e
185
+
186
+ def _parse_webhook(self, data: dict) -> Webhook:
187
+ """Parse webhook data from API response"""
188
+ return Webhook(
189
+ id=data["id"],
190
+ url=data["url"],
191
+ events=data["events"],
192
+ active=data["active"],
193
+ created_at=data["createdAt"],
194
+ secret=data.get("secret")
195
+ )