@@ -16,17 +16,21 @@ def __init__(self, api_key: str, base_url: str = "https://api.keymint.dev"):
1616 'Content-Type' : 'application/json'
1717 }
1818
19- def _handle_request (self , method : str , endpoint : str , params : dict = None , query_params : dict = None ):
19+ def _handle_request (self , method : str , endpoint : str , params : dict = None , query_params : dict = None , idempotency_key : str = None ):
2020 url = f'{ self .base_url } { endpoint } '
21+ headers = self .headers .copy ()
22+ if idempotency_key :
23+ headers ['Idempotency-Key' ] = idempotency_key
24+
2125 try :
2226 if method .upper () == 'GET' :
23- response = requests .get (url , params = query_params , headers = self . headers )
27+ response = requests .get (url , params = query_params , headers = headers )
2428 elif method .upper () == 'POST' :
25- response = requests .post (url , json = params , headers = self . headers )
29+ response = requests .post (url , json = params , headers = headers )
2630 elif method .upper () == 'PUT' :
27- response = requests .put (url , json = params , headers = self . headers )
31+ response = requests .put (url , json = params , headers = headers )
2832 elif method .upper () == 'DELETE' :
29- response = requests .delete (url , params = query_params , headers = self . headers )
33+ response = requests .delete (url , params = query_params , headers = headers )
3034 else :
3135 raise ValueError (f"Unsupported HTTP method: { method } " )
3236
@@ -49,15 +53,16 @@ def _handle_request(self, method: str, endpoint: str, params: dict = None, query
4953 except Exception as err :
5054 raise KeyMintApiError (message = str (err ), code = - 1 )
5155
52- def create_key (self , params : CreateKeyParams ) -> CreateKeyResponse :
56+ def create_key (self , params : CreateKeyParams , idempotency_key : str = None ) -> CreateKeyResponse :
5357 """
5458 Creates a new license key.
5559 :param params: Parameters for creating the key.
60+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
5661 :returns: The created key information.
5762 """
58- return self ._handle_request ('POST' , '/key' , params )
63+ return self ._handle_request ('POST' , '/key' , params , idempotency_key = idempotency_key )
5964
60- def activate_key (self , params : ActivateKeyParams ) -> ActivateKeyResponse :
65+ def activate_key (self , params : ActivateKeyParams , idempotency_key : str = None ) -> ActivateKeyResponse :
6166 """
6267 Activates a license key for a specific device.
6368
@@ -67,41 +72,46 @@ def activate_key(self, params: ActivateKeyParams) -> ActivateKeyResponse:
6772 MUST cache the validation result locally.
6873
6974 :param params: Activation parameters including productId, licenseKey, and optional hostId.
75+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
7076 :return: Activation success message and licensee information.
7177 """
72- return self ._handle_request ('POST' , '/key/activate' , params )
78+ return self ._handle_request ('POST' , '/key/activate' , params , idempotency_key = idempotency_key )
7379
74- def deactivate_key (self , params : DeactivateKeyParams ) -> DeactivateKeyResponse :
80+ def deactivate_key (self , params : DeactivateKeyParams , idempotency_key : str = None ) -> DeactivateKeyResponse :
7581 """
7682 Deactivates a device from a license key.
7783 :param params: Parameters for deactivating the key.
84+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
7885 :returns: The deactivation confirmation.
7986 """
80- return self ._handle_request ('POST' , '/key/deactivate' , params )
87+ return self ._handle_request ('POST' , '/key/deactivate' , params , idempotency_key = idempotency_key )
8188
82- def floating_checkout (self , params : FloatingCheckoutParams ) -> FloatingCheckoutResponse :
89+ def floating_checkout (self , params : FloatingCheckoutParams , idempotency_key : str = None ) -> FloatingCheckoutResponse :
8390 """
8491 Checks out a floating license seat.
8592 :param params: Parameters for checking out the license.
93+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
8694 :returns: The checkout response containing sessionId and sessionSecret.
8795 """
88- return self ._handle_request ('POST' , '/key/checkout' , params )
96+ return self ._handle_request ('POST' , '/key/checkout' , params , idempotency_key = idempotency_key )
8997
90- def floating_heartbeat (self , params : FloatingHeartbeatParams ) -> FloatingHeartbeatResponse :
98+ def floating_heartbeat (self , params : FloatingHeartbeatParams , idempotency_key : str = None ) -> FloatingHeartbeatResponse :
9199 """
92100 Sends a heartbeat to keep a floating license session alive.
93101 :param params: Parameters for the heartbeat (includes rotating signature).
102+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
94103 :returns: The heartbeat response with extended expiry and new nonce.
95104 """
96- return self ._handle_request ('POST' , '/key/heartbeat' , params )
105+ return self ._handle_request ('POST' , '/key/heartbeat' , params , idempotency_key = idempotency_key )
97106
98- def floating_checkin (self , params : FloatingCheckinParams ) -> FloatingCheckinResponse :
107+ def floating_checkin (self , params : FloatingCheckinParams , idempotency_key : str = None ) -> FloatingCheckinResponse :
99108 """
100109 Checks in a floating license session, releasing the seat.
101110 :param params: Parameters for checking in the license (includes rotating signature).
111+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
102112 :returns: The checkin confirmation.
103113 """
104- return self ._handle_request ('POST' , '/key/checkin' , params )
114+ return self ._handle_request ('POST' , '/key/checkin' , params , idempotency_key = idempotency_key )
105115
106116 def get_key (self , params : GetKeyParams ) -> GetKeyResponse :
107117 """
@@ -115,31 +125,34 @@ def get_key(self, params: GetKeyParams) -> GetKeyResponse:
115125 }
116126 return self ._handle_request ('GET' , '/key' , query_params = query_params )
117127
118- def block_key (self , params : BlockKeyParams ) -> BlockKeyResponse :
128+ def block_key (self , params : BlockKeyParams , idempotency_key : str = None ) -> BlockKeyResponse :
119129 """
120130 Blocks a specific license key.
121131 :param params: Parameters for blocking the key.
132+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
122133 :returns: The block confirmation.
123134 """
124- return self ._handle_request ('POST' , '/key/block' , params )
135+ return self ._handle_request ('POST' , '/key/block' , params , idempotency_key = idempotency_key )
125136
126- def unblock_key (self , params : UnblockKeyParams ) -> UnblockKeyResponse :
137+ def unblock_key (self , params : UnblockKeyParams , idempotency_key : str = None ) -> UnblockKeyResponse :
127138 """
128139 Unblocks a previously blocked license key.
129140 :param params: Parameters for unblocking the key.
141+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
130142 :returns: The unblock confirmation.
131143 """
132- return self ._handle_request ('POST' , '/key/unblock' , params )
144+ return self ._handle_request ('POST' , '/key/unblock' , params , idempotency_key = idempotency_key )
133145
134146 # Customer Management Methods
135147
136- def create_customer (self , params : CreateCustomerParams ) -> CreateCustomerResponse :
148+ def create_customer (self , params : CreateCustomerParams , idempotency_key : str = None ) -> CreateCustomerResponse :
137149 """
138150 Creates a new customer.
139151 :param params: Parameters for creating the customer.
152+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
140153 :returns: The created customer information.
141154 """
142- return self ._handle_request ('POST' , '/customer' , params )
155+ return self ._handle_request ('POST' , '/customer' , params , idempotency_key = idempotency_key )
143156
144157 def get_all_customers (self , params : Optional [GetAllCustomersParams ] = None ) -> GetAllCustomersResponse :
145158 """
@@ -158,22 +171,24 @@ def get_customer_by_id(self, params: GetCustomerByIdParams) -> GetCustomerByIdRe
158171 query_params = {'customerId' : params ['customerId' ]}
159172 return self ._handle_request ('GET' , '/customer/by-id' , query_params = query_params )
160173
161- def update_customer (self , params : UpdateCustomerParams ) -> UpdateCustomerResponse :
174+ def update_customer (self , params : UpdateCustomerParams , idempotency_key : str = None ) -> UpdateCustomerResponse :
162175 """
163176 Updates an existing customer's information.
164177 :param params: Parameters for updating the customer.
178+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
165179 :returns: The update confirmation.
166180 """
167- return self ._handle_request ('PUT' , '/customer/by-id' , params )
181+ return self ._handle_request ('PUT' , '/customer/by-id' , params , idempotency_key = idempotency_key )
168182
169- def delete_customer (self , params : DeleteCustomerParams ) -> DeleteCustomerResponse :
183+ def delete_customer (self , params : DeleteCustomerParams , idempotency_key : str = None ) -> DeleteCustomerResponse :
170184 """
171185 Permanently deletes a customer and all associated license keys.
172186 :param params: Parameters containing the customer ID.
187+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
173188 :returns: The deletion confirmation.
174189 """
175190 query_params = {'customerId' : params ['customerId' ]}
176- return self ._handle_request ('DELETE' , '/customer/by-id' , query_params = query_params )
191+ return self ._handle_request ('DELETE' , '/customer/by-id' , query_params = query_params , idempotency_key = idempotency_key )
177192
178193 def get_customer_with_keys (self , params : GetCustomerWithKeysParams ) -> GetCustomerWithKeysResponse :
179194 """
@@ -184,14 +199,15 @@ def get_customer_with_keys(self, params: GetCustomerWithKeysParams) -> GetCustom
184199 query_params = {'customerId' : params ['customerId' ]}
185200 return self ._handle_request ('GET' , '/customer/keys' , query_params = query_params )
186201
187- def toggle_customer_status (self , params : ToggleCustomerStatusParams ) -> ToggleCustomerStatusResponse :
202+ def toggle_customer_status (self , params : ToggleCustomerStatusParams , idempotency_key : str = None ) -> ToggleCustomerStatusResponse :
188203 """
189204 Toggles the active status of a customer account (disable or enable).
190205 :param params: Parameters containing the customer ID.
206+ :param idempotency_key: Optional unique identifier to ensure request idempotency.
191207 :returns: The status toggle confirmation.
192208 """
193209 query_params = {'customerId' : params ['customerId' ]}
194- return self ._handle_request ('POST' , '/customer/disable' , params = None , query_params = query_params )
210+ return self ._handle_request ('POST' , '/customer/disable' , params = None , query_params = query_params , idempotency_key = idempotency_key )
195211
196212 @staticmethod
197213 def verify_webhook_signature (payload : str , header : str , secret : str , tolerance_seconds : int = 300 ) -> bool :
0 commit comments