Bug: Create an id type for API Key ID



Currently `key_id` is a String. We need to create an `id_type` for it and migrate all occurrences accordingly
diff --git a/.typos.toml b/.typos.toml
index 79c86a39c6b..d2ffb8a5b10 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -1,5 +1,8 @@
 [default]
 check-filename = true
+extend-ignore-identifiers-re = [
+    "UE_[0-9]{3,4}", # Unified error codes
+]
 
 [default.extend-identifiers]
 ABD = "ABD"  # Aberdeenshire, UK ISO 3166-2 code
@@ -38,7 +41,6 @@ ws2ipdef = "ws2ipdef" # WinSock Extension
 ws2tcpip = "ws2tcpip" # WinSock Extension
 ZAR = "ZAR"  # South African Rand currency code
 JOD = "JOD" # Jordan currency code
-UE_000 = "UE_000" #default unified error code
 
 
 [default.extend-words]
diff --git a/crates/api_models/src/api_keys.rs b/crates/api_models/src/api_keys.rs
index 65cc6b9a25a..d25cd989b0f 100644
--- a/crates/api_models/src/api_keys.rs
+++ b/crates/api_models/src/api_keys.rs
@@ -29,8 +29,8 @@ pub struct CreateApiKeyRequest {
 #[derive(Debug, Serialize, ToSchema)]
 pub struct CreateApiKeyResponse {
     /// The identifier for the API Key.
-    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")]
-    pub key_id: String,
+    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
+    pub key_id: common_utils::id_type::ApiKeyId,
 
     /// The identifier for the Merchant Account.
     #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)]
@@ -72,8 +72,8 @@ pub struct CreateApiKeyResponse {
 #[derive(Debug, Serialize, ToSchema)]
 pub struct RetrieveApiKeyResponse {
     /// The identifier for the API Key.
-    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")]
-    pub key_id: String,
+    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
+    pub key_id: common_utils::id_type::ApiKeyId,
 
     /// The identifier for the Merchant Account.
     #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)]
@@ -131,7 +131,8 @@ pub struct UpdateApiKeyRequest {
     pub expiration: Option<ApiKeyExpiration>,
 
     #[serde(skip_deserializing)]
-    pub key_id: String,
+    #[schema(value_type = String)]
+    pub key_id: common_utils::id_type::ApiKeyId,
 
     #[serde(skip_deserializing)]
     #[schema(value_type = String)]
@@ -146,8 +147,8 @@ pub struct RevokeApiKeyResponse {
     pub merchant_id: common_utils::id_type::MerchantId,
 
     /// The identifier for the API Key.
-    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")]
-    pub key_id: String,
+    #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
+    pub key_id: common_utils::id_type::ApiKeyId,
     /// Indicates whether the API key was revoked or not.
     #[schema(example = "true")]
     pub revoked: bool,
diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs
index 2b1571617e8..ffeff7f2f2a 100644
--- a/crates/common_utils/src/events.rs
+++ b/crates/common_utils/src/events.rs
@@ -40,6 +40,9 @@ pub enum ApiEventsType {
     BusinessProfile {
         profile_id: id_type::ProfileId,
     },
+    ApiKey {
+        key_id: id_type::ApiKeyId,
+    },
     User {
         user_id: String,
     },
@@ -124,10 +127,6 @@ impl_api_event_type!(
     (
         String,
         id_type::MerchantId,
-        (id_type::MerchantId, String),
-        (id_type::MerchantId, &String),
-        (&id_type::MerchantId, &String),
-        (&String, &String),
         (Option<i64>, Option<i64>, String),
         (Option<i64>, Option<i64>, id_type::MerchantId),
         bool
diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs
index 78e3841690a..c256559fc1c 100644
--- a/crates/common_utils/src/id_type.rs
+++ b/crates/common_utils/src/id_type.rs
@@ -3,6 +3,7 @@
 
 use std::{borrow::Cow, fmt::Debug};
 
+mod api_key;
 mod customer;
 mod merchant;
 mod merchant_connector_account;
@@ -14,6 +15,7 @@ mod routing;
 #[cfg(feature = "v2")]
 mod global_id;
 
+pub use api_key::ApiKeyId;
 pub use customer::CustomerId;
 use diesel::{
     backend::Backend,
diff --git a/crates/common_utils/src/id_type/api_key.rs b/crates/common_utils/src/id_type/api_key.rs
new file mode 100644
index 00000000000..f252846e6ac
--- /dev/null
+++ b/crates/common_utils/src/id_type/api_key.rs
@@ -0,0 +1,46 @@
+crate::id_type!(
+    ApiKeyId,
+    "A type for key_id that can be used for API key IDs"
+);
+crate::impl_id_type_methods!(ApiKeyId, "key_id");
+
+// This is to display the `ApiKeyId` as ApiKeyId(abcd)
+crate::impl_debug_id_type!(ApiKeyId);
+crate::impl_try_from_cow_str_id_type!(ApiKeyId, "key_id");
+
+crate::impl_serializable_secret_id_type!(ApiKeyId);
+crate::impl_queryable_id_type!(ApiKeyId);
+crate::impl_to_sql_from_sql_id_type!(ApiKeyId);
+
+impl ApiKeyId {
+    /// Generate Api Key Id from prefix
+    pub fn generate_key_id(prefix: &'static str) -> Self {
+        Self(crate::generate_ref_id_with_default_length(prefix))
+    }
+}
+
+impl crate::events::ApiEventMetric for ApiKeyId {
+    fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
+        Some(crate::events::ApiEventsType::ApiKey {
+            key_id: self.clone(),
+        })
+    }
+}
+
+impl crate::events::ApiEventMetric for (super::MerchantId, ApiKeyId) {
+    fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
+        Some(crate::events::ApiEventsType::ApiKey {
+            key_id: self.1.clone(),
+        })
+    }
+}
+
+impl crate::events::ApiEventMetric for (&super::MerchantId, &ApiKeyId) {
+    fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
+        Some(crate::events::ApiEventsType::ApiKey {
+            key_id: self.1.clone(),
+        })
+    }
+}
+
+crate::impl_default_id_type!(ApiKeyId, "key");
diff --git a/crates/diesel_models/src/api_keys.rs b/crates/diesel_models/src/api_keys.rs
index 1781e65cded..7076bf597e0 100644
--- a/crates/diesel_models/src/api_keys.rs
+++ b/crates/diesel_models/src/api_keys.rs
@@ -9,7 +9,7 @@ use crate::schema::api_keys;
 )]
 #[diesel(table_name = api_keys, primary_key(key_id), check_for_backend(diesel::pg::Pg))]
 pub struct ApiKey {
-    pub key_id: String,
+    pub key_id: common_utils::id_type::ApiKeyId,
     pub merchant_id: common_utils::id_type::MerchantId,
     pub name: String,
     pub description: Option<String>,
@@ -23,7 +23,7 @@ pub struct ApiKey {
 #[derive(Debug, Insertable)]
 #[diesel(table_name = api_keys)]
 pub struct ApiKeyNew {
-    pub key_id: String,
+    pub key_id: common_utils::id_type::ApiKeyId,
     pub merchant_id: common_utils::id_type::MerchantId,
     pub name: String,
     pub description: Option<String>,
@@ -141,7 +141,7 @@ mod diesel_impl {
 // Tracking data by process_tracker
 #[derive(Default, Debug, Deserialize, Serialize, Clone)]
 pub struct ApiKeyExpiryTrackingData {
-    pub key_id: String,
+    pub key_id: common_utils::id_type::ApiKeyId,
     pub merchant_id: common_utils::id_type::MerchantId,
     pub api_key_name: String,
     pub prefix: String,
diff --git a/crates/diesel_models/src/query/api_keys.rs b/crates/diesel_models/src/query/api_keys.rs
index 5dd14501025..479e226c1d3 100644
--- a/crates/diesel_models/src/query/api_keys.rs
+++ b/crates/diesel_models/src/query/api_keys.rs
@@ -18,7 +18,7 @@ impl ApiKey {
     pub async fn update_by_merchant_id_key_id(
         conn: &PgPooledConn,
         merchant_id: common_utils::id_type::MerchantId,
-        key_id: String,
+        key_id: common_utils::id_type::ApiKeyId,
         api_key_update: ApiKeyUpdate,
     ) -> StorageResult<Self> {
         match generics::generic_update_with_unique_predicate_get_result::<
@@ -57,7 +57,7 @@ impl ApiKey {
     pub async fn revoke_by_merchant_id_key_id(
         conn: &PgPooledConn,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> StorageResult<bool> {
         generics::generic_delete::<<Self as HasTable>::Table, _>(
             conn,
@@ -71,7 +71,7 @@ impl ApiKey {
     pub async fn find_optional_by_merchant_id_key_id(
         conn: &PgPooledConn,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> StorageResult<Option<Self>> {
         generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
             conn,
diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs
index 6e907dbd91b..de4a8931c78 100644
--- a/crates/router/src/core/api_keys.rs
+++ b/crates/router/src/core/api_keys.rs
@@ -13,7 +13,6 @@ use crate::{
     routes::{metrics, SessionState},
     services::{authentication, ApplicationResponse},
     types::{api, storage, transformers::ForeignInto},
-    utils,
 };
 
 #[cfg(feature = "email")]
@@ -63,9 +62,9 @@ impl PlaintextApiKey {
         Self(format!("{env}_{key}").into())
     }
 
-    pub fn new_key_id() -> String {
+    pub fn new_key_id() -> common_utils::id_type::ApiKeyId {
         let env = router_env::env::prefix_for_env();
-        utils::generate_id(consts::ID_LENGTH, env)
+        common_utils::id_type::ApiKeyId::generate_key_id(env)
     }
 
     pub fn prefix(&self) -> String {
@@ -223,7 +222,7 @@ pub async fn add_api_key_expiry_task(
         expiry_reminder_days: expiry_reminder_days.clone(),
     };
 
-    let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str());
+    let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);
     let process_tracker_entry = storage::ProcessTrackerNew::new(
         process_tracker_id,
         API_KEY_EXPIRY_NAME,
@@ -241,7 +240,7 @@ pub async fn add_api_key_expiry_task(
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable_lazy(|| {
             format!(
-                "Failed while inserting API key expiry reminder to process_tracker: api_key_id: {}",
+                "Failed while inserting API key expiry reminder to process_tracker: {:?}",
                 api_key.key_id
             )
         })?;
@@ -258,11 +257,11 @@ pub async fn add_api_key_expiry_task(
 pub async fn retrieve_api_key(
     state: SessionState,
     merchant_id: common_utils::id_type::MerchantId,
-    key_id: &str,
+    key_id: common_utils::id_type::ApiKeyId,
 ) -> RouterResponse<api::RetrieveApiKeyResponse> {
     let store = state.store.as_ref();
     let api_key = store
-        .find_api_key_by_merchant_id_key_id_optional(&merchant_id, key_id)
+        .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id)
         .await
         .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
         .attach_printable("Failed to retrieve API key")?
@@ -388,7 +387,7 @@ pub async fn update_api_key_expiry_task(
         }
     }
 
-    let task_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str());
+    let task_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);
 
     let task_ids = vec![task_id.clone()];
 
@@ -430,7 +429,7 @@ pub async fn update_api_key_expiry_task(
 pub async fn revoke_api_key(
     state: SessionState,
     merchant_id: &common_utils::id_type::MerchantId,
-    key_id: &str,
+    key_id: &common_utils::id_type::ApiKeyId,
 ) -> RouterResponse<api::RevokeApiKeyResponse> {
     let store = state.store.as_ref();
 
@@ -496,7 +495,7 @@ pub async fn revoke_api_key(
 #[instrument(skip_all)]
 pub async fn revoke_api_key_expiry_task(
     store: &dyn crate::db::StorageInterface,
-    key_id: &str,
+    key_id: &common_utils::id_type::ApiKeyId,
 ) -> Result<(), errors::ProcessTrackerError> {
     let task_id = generate_task_id_for_api_key_expiry_workflow(key_id);
     let task_ids = vec![task_id];
@@ -535,8 +534,13 @@ pub async fn list_api_keys(
 }
 
 #[cfg(feature = "email")]
-fn generate_task_id_for_api_key_expiry_workflow(key_id: &str) -> String {
-    format!("{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{key_id}")
+fn generate_task_id_for_api_key_expiry_workflow(
+    key_id: &common_utils::id_type::ApiKeyId,
+) -> String {
+    format!(
+        "{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{}",
+        key_id.get_string_repr()
+    )
 }
 
 impl From<&str> for PlaintextApiKey {
diff --git a/crates/router/src/db/api_keys.rs b/crates/router/src/db/api_keys.rs
index 62d261aa3c4..0d3ec5dc8c2 100644
--- a/crates/router/src/db/api_keys.rs
+++ b/crates/router/src/db/api_keys.rs
@@ -20,20 +20,20 @@ pub trait ApiKeyInterface {
     async fn update_api_key(
         &self,
         merchant_id: common_utils::id_type::MerchantId,
-        key_id: String,
+        key_id: common_utils::id_type::ApiKeyId,
         api_key: storage::ApiKeyUpdate,
     ) -> CustomResult<storage::ApiKey, errors::StorageError>;
 
     async fn revoke_api_key(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<bool, errors::StorageError>;
 
     async fn find_api_key_by_merchant_id_key_id_optional(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;
 
     async fn find_api_key_by_hash_optional(
@@ -67,7 +67,7 @@ impl ApiKeyInterface for Store {
     async fn update_api_key(
         &self,
         merchant_id: common_utils::id_type::MerchantId,
-        key_id: String,
+        key_id: common_utils::id_type::ApiKeyId,
         api_key: storage::ApiKeyUpdate,
     ) -> CustomResult<storage::ApiKey, errors::StorageError> {
         let conn = connection::pg_connection_write(self).await?;
@@ -99,7 +99,8 @@ impl ApiKeyInterface for Store {
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))?
             .ok_or(report!(errors::StorageError::ValueNotFound(format!(
-                "ApiKey of {_key_id} not found"
+                "ApiKey of {} not found",
+                _key_id.get_string_repr()
             ))))?;
 
             cache::publish_and_redact(
@@ -115,7 +116,7 @@ impl ApiKeyInterface for Store {
     async fn revoke_api_key(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<bool, errors::StorageError> {
         let conn = connection::pg_connection_write(self).await?;
         let delete_call = || async {
@@ -141,7 +142,8 @@ impl ApiKeyInterface for Store {
                     .await
                     .map_err(|error| report!(errors::StorageError::from(error)))?
                     .ok_or(report!(errors::StorageError::ValueNotFound(format!(
-                        "ApiKey of {key_id} not found"
+                        "ApiKey of {} not found",
+                        key_id.get_string_repr()
                     ))))?;
 
             cache::publish_and_redact(
@@ -157,7 +159,7 @@ impl ApiKeyInterface for Store {
     async fn find_api_key_by_merchant_id_key_id_optional(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
         storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
@@ -240,7 +242,7 @@ impl ApiKeyInterface for MockDb {
     async fn update_api_key(
         &self,
         merchant_id: common_utils::id_type::MerchantId,
-        key_id: String,
+        key_id: common_utils::id_type::ApiKeyId,
         api_key: storage::ApiKeyUpdate,
     ) -> CustomResult<storage::ApiKey, errors::StorageError> {
         let mut locked_api_keys = self.api_keys.lock().await;
@@ -282,13 +284,13 @@ impl ApiKeyInterface for MockDb {
     async fn revoke_api_key(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<bool, errors::StorageError> {
         let mut locked_api_keys = self.api_keys.lock().await;
         // find the key to remove, if it exists
         if let Some(pos) = locked_api_keys
             .iter()
-            .position(|k| k.merchant_id == *merchant_id && k.key_id == key_id)
+            .position(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
         {
             // use `remove` instead of `swap_remove` so we have a consistent order, which might
             // matter to someone using limit/offset in `list_api_keys_by_merchant_id`
@@ -302,14 +304,14 @@ impl ApiKeyInterface for MockDb {
     async fn find_api_key_by_merchant_id_key_id_optional(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        key_id: &str,
+        key_id: &common_utils::id_type::ApiKeyId,
     ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
         Ok(self
             .api_keys
             .lock()
             .await
             .iter()
-            .find(|k| k.merchant_id == *merchant_id && k.key_id == key_id)
+            .find(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
             .cloned())
     }
 
@@ -397,9 +399,16 @@ mod tests {
         let merchant_id =
             common_utils::id_type::MerchantId::try_from(Cow::from("merchant1")).unwrap();
 
+        let key_id1 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id1")).unwrap();
+
+        let key_id2 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id2")).unwrap();
+
+        let non_existent_key_id =
+            common_utils::id_type::ApiKeyId::try_from(Cow::from("does_not_exist")).unwrap();
+
         let key1 = mockdb
             .insert_api_key(storage::ApiKeyNew {
-                key_id: "key_id1".into(),
+                key_id: key_id1.clone(),
                 merchant_id: merchant_id.clone(),
                 name: "Key 1".into(),
                 description: None,
@@ -414,7 +423,7 @@ mod tests {
 
         mockdb
             .insert_api_key(storage::ApiKeyNew {
-                key_id: "key_id2".into(),
+                key_id: key_id2.clone(),
                 merchant_id: merchant_id.clone(),
                 name: "Key 2".into(),
                 description: None,
@@ -428,13 +437,13 @@ mod tests {
             .unwrap();
 
         let found_key1 = mockdb
-            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, "key_id1")
+            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
             .await
             .unwrap()
             .unwrap();
         assert_eq!(found_key1.key_id, key1.key_id);
         assert!(mockdb
-            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, "does_not_exist")
+            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &non_existent_key_id)
             .await
             .unwrap()
             .is_none());
@@ -442,7 +451,7 @@ mod tests {
         mockdb
             .update_api_key(
                 merchant_id.clone(),
-                "key_id1".into(),
+                key_id1.clone(),
                 storage::ApiKeyUpdate::LastUsedUpdate {
                     last_used: datetime!(2023-02-04 1:11),
                 },
@@ -450,7 +459,7 @@ mod tests {
             .await
             .unwrap();
         let updated_key1 = mockdb
-            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, "key_id1")
+            .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
             .await
             .unwrap()
             .unwrap();
@@ -464,10 +473,7 @@ mod tests {
                 .len(),
             2
         );
-        mockdb
-            .revoke_api_key(&merchant_id, "key_id1")
-            .await
-            .unwrap();
+        mockdb.revoke_api_key(&merchant_id, &key_id1).await.unwrap();
         assert_eq!(
             mockdb
                 .list_api_keys_by_merchant_id(&merchant_id, None, None)
@@ -495,8 +501,10 @@ mod tests {
             .await
             .unwrap();
 
+        let test_key = common_utils::id_type::ApiKeyId::try_from(Cow::from("test_ey")).unwrap();
+
         let api = storage::ApiKeyNew {
-            key_id: "test_key".into(),
+            key_id: test_key.clone(),
             merchant_id: merchant_id.clone(),
             name: "My test key".into(),
             description: None,
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 6e9daeedc59..208cd8d5f97 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -226,7 +226,7 @@ impl ApiKeyInterface for KafkaStore {
     async fn update_api_key(
         &self,
         merchant_id: id_type::MerchantId,
-        key_id: String,
+        key_id: id_type::ApiKeyId,
         api_key: storage::ApiKeyUpdate,
     ) -> CustomResult<storage::ApiKey, errors::StorageError> {
         self.diesel_store
@@ -237,7 +237,7 @@ impl ApiKeyInterface for KafkaStore {
     async fn revoke_api_key(
         &self,
         merchant_id: &id_type::MerchantId,
-        key_id: &str,
+        key_id: &id_type::ApiKeyId,
     ) -> CustomResult<bool, errors::StorageError> {
         self.diesel_store.revoke_api_key(merchant_id, key_id).await
     }
@@ -245,7 +245,7 @@ impl ApiKeyInterface for KafkaStore {
     async fn find_api_key_by_merchant_id_key_id_optional(
         &self,
         merchant_id: &id_type::MerchantId,
-        key_id: &str,
+        key_id: &id_type::ApiKeyId,
     ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
         self.diesel_store
             .find_api_key_by_merchant_id_key_id_optional(merchant_id, key_id)
diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs
index 047e0d9b886..bbecaae9e80 100644
--- a/crates/router/src/routes/api_keys.rs
+++ b/crates/router/src/routes/api_keys.rs
@@ -79,7 +79,7 @@ pub async fn api_key_create(
 pub async fn api_key_retrieve(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<String>,
+    path: web::Path<common_utils::id_type::ApiKeyId>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyRetrieve;
     let key_id = path.into_inner();
@@ -93,7 +93,7 @@ pub async fn api_key_retrieve(
             api_keys::retrieve_api_key(
                 state,
                 auth_data.merchant_account.get_id().to_owned(),
-                key_id,
+                key_id.to_owned(),
             )
         },
         auth::auth_type(
@@ -114,7 +114,10 @@ pub async fn api_key_retrieve(
 pub async fn api_key_retrieve(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<(common_utils::id_type::MerchantId, String)>,
+    path: web::Path<(
+        common_utils::id_type::MerchantId,
+        common_utils::id_type::ApiKeyId,
+    )>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyRetrieve;
     let (merchant_id, key_id) = path.into_inner();
@@ -123,7 +126,7 @@ pub async fn api_key_retrieve(
         flow,
         state,
         &req,
-        (merchant_id.clone(), &key_id),
+        (merchant_id.clone(), key_id.clone()),
         |state, _, (merchant_id, key_id), _| api_keys::retrieve_api_key(state, merchant_id, key_id),
         auth::auth_type(
             &auth::AdminApiAuth,
@@ -144,7 +147,10 @@ pub async fn api_key_retrieve(
 pub async fn api_key_update(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<(common_utils::id_type::MerchantId, String)>,
+    path: web::Path<(
+        common_utils::id_type::MerchantId,
+        common_utils::id_type::ApiKeyId,
+    )>,
     json_payload: web::Json<api_types::UpdateApiKeyRequest>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyUpdate;
@@ -177,7 +183,7 @@ pub async fn api_key_update(
 pub async fn api_key_update(
     state: web::Data<AppState>,
     req: HttpRequest,
-    key_id: web::Path<String>,
+    key_id: web::Path<common_utils::id_type::ApiKeyId>,
     json_payload: web::Json<api_types::UpdateApiKeyRequest>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyUpdate;
@@ -212,7 +218,10 @@ pub async fn api_key_update(
 pub async fn api_key_revoke(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<(common_utils::id_type::MerchantId, String)>,
+    path: web::Path<(
+        common_utils::id_type::MerchantId,
+        common_utils::id_type::ApiKeyId,
+    )>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyRevoke;
     let (merchant_id, key_id) = path.into_inner();
@@ -242,7 +251,10 @@ pub async fn api_key_revoke(
 pub async fn api_key_revoke(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<(common_utils::id_type::MerchantId, String)>,
+    path: web::Path<(
+        common_utils::id_type::MerchantId,
+        common_utils::id_type::ApiKeyId,
+    )>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyRevoke;
     let (merchant_id, key_id) = path.into_inner();
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 3731d8ee081..9be4c493874 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -82,7 +82,7 @@ pub struct AuthenticationDataWithUser {
 pub enum AuthenticationType {
     ApiKey {
         merchant_id: id_type::MerchantId,
-        key_id: String,
+        key_id: id_type::ApiKeyId,
     },
     AdminApiKey,
     AdminApiAuthWithMerchantId {
diff --git a/crates/router/src/services/authentication/decision.rs b/crates/router/src/services/authentication/decision.rs
index d665b0caaf4..c31a4696bc9 100644
--- a/crates/router/src/services/authentication/decision.rs
+++ b/crates/router/src/services/authentication/decision.rs
@@ -67,7 +67,7 @@ pub enum Identifiers {
     /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`]
     ApiKey {
         merchant_id: common_utils::id_type::MerchantId,
-        key_id: String,
+        key_id: common_utils::id_type::ApiKeyId,
     },
     /// [`PublishableKey`] is an authentication method that uses a publishable key. This is used with [`PublishableKey`]
     PublishableKey { merchant_id: String },
@@ -80,7 +80,7 @@ pub async fn add_api_key(
     state: &SessionState,
     api_key: Secret<String>,
     merchant_id: common_utils::id_type::MerchantId,
-    key_id: String,
+    key_id: common_utils::id_type::ApiKeyId,
     expiry: Option<u64>,
 ) -> CustomResult<(), ApiClientError> {
     let decision_config = if let Some(config) = &state.conf.decision {
diff --git a/crates/router/src/services/authentication/detached.rs b/crates/router/src/services/authentication/detached.rs
index af373d3e559..9d77d2b1f07 100644
--- a/crates/router/src/services/authentication/detached.rs
+++ b/crates/router/src/services/authentication/detached.rs
@@ -1,7 +1,10 @@
 use std::{borrow::Cow, string::ToString};
 
 use actix_web::http::header::HeaderMap;
-use common_utils::{crypto::VerifySignature, id_type::MerchantId};
+use common_utils::{
+    crypto::VerifySignature,
+    id_type::{ApiKeyId, MerchantId},
+};
 use error_stack::ResultExt;
 use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
 
@@ -16,7 +19,7 @@ const HEADER_CHECKSUM: &str = "x-checksum";
 pub struct ExtractedPayload {
     pub payload_type: PayloadType,
     pub merchant_id: Option<MerchantId>,
-    pub key_id: Option<String>,
+    pub key_id: Option<ApiKeyId>,
 }
 
 #[derive(strum::EnumString, strum::Display, PartialEq, Debug)]
@@ -61,13 +64,19 @@ impl ExtractedPayload {
                 message: format!("`{}` header not present", HEADER_AUTH_TYPE),
             })?;
 
+        let key_id = headers
+            .get(HEADER_KEY_ID)
+            .and_then(|value| value.to_str().ok())
+            .map(|key_id| ApiKeyId::try_from(Cow::from(key_id.to_string())))
+            .transpose()
+            .change_context(ApiErrorResponse::InvalidRequestData {
+                message: format!("`{}` header is invalid or not present", HEADER_KEY_ID),
+            })?;
+
         Ok(Self {
             payload_type: auth_type,
             merchant_id: Some(merchant_id),
-            key_id: headers
-                .get(HEADER_KEY_ID)
-                .and_then(|v| v.to_str().ok())
-                .map(|v| v.to_string()),
+            key_id,
         })
     }
 
@@ -95,7 +104,7 @@ impl ExtractedPayload {
             &self
                 .merchant_id
                 .as_ref()
-                .map(|inner| append_option(inner.get_string_repr(), &self.key_id)),
+                .map(|inner| append_api_key(inner.get_string_repr(), &self.key_id)),
         )
     }
 }
@@ -107,3 +116,11 @@ fn append_option(prefix: &str, data: &Option<String>) -> String {
         None => prefix.to_string(),
     }
 }
+
+#[inline]
+fn append_api_key(prefix: &str, data: &Option<ApiKeyId>) -> String {
+    match data {
+        Some(inner) => format!("{}:{}", prefix, inner.get_string_repr()),
+        None => prefix.to_string(),
+    }
+}

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

1a. Create API key
```
curl --location 'http://localhost:8080/api_keys/merchant_1729063721' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
  "name": "API Key 1",
  "description": null,
  "expiration": "2038-01-19T03:14:08.000Z"
}'
```

1b. Response
```json
{
    "key_id": "dev_ZfucbDHJm7pbcFjsoq5I",
    "merchant_id": "merchant_1729063721",
    "name": "API Key 1",
    "description": null,
    "api_key": "dev_sNyAhsbOkEYtuuUvMOzlTIMpTZ3Kd9AC5sboxRuQOMX5xKyqTDn0oG9Vaq4XGwN1",
    "created": "2024-10-16T07:28:44.906Z",
    "expiration": "2038-01-19T03:14:08.000Z"
}
```

2a. Update API Key
```
curl --location 'http://localhost:8080/api_keys/merchant_1729063721/dev_ZfucbDHJm7pbcFjsoq5I' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
  "name": null,
  "description": "My very awesome API key",
  "expiration": null
}'
```

2b. Reponse
```json
{
    "key_id": "dev_ZfucbDHJm7pbcFjsoq5I",
    "merchant_id": "merchant_1729063721",
    "name": "API Key 1",
    "description": "My very awesome API key",
    "prefix": "dev_sNyAhsbO",
    "created": "2024-10-16T07:28:44.906Z",
    "expiration": "2038-01-19T03:14:08.000Z"
}
```

3a. Retrieve API Key
```
curl --location 'http://localhost:8080/api_keys/merchant_1729063721/dev_ZfucbDHJm7pbcFjsoq5I' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin'
```

3b. Response
```json
{
    "key_id": "dev_ZfucbDHJm7pbcFjsoq5I",
    "merchant_id": "merchant_1729063721",
    "name": "API Key 1",
    "description": "My very awesome API key",
    "prefix": "dev_sNyAhsbO",
    "created": "2024-10-16T07:28:44.906Z",
    "expiration": "2038-01-19T03:14:08.000Z"
}
```

4a. Delete API Key
```
curl --location --request DELETE 'http://localhost:8080/api_keys/merchant_1729063721/dev_ZfucbDHJm7pbcFjsoq5I' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin'
```

4b. Response
```json
{
    "merchant_id": "merchant_1729063721",
    "key_id": "dev_ZfucbDHJm7pbcFjsoq5I",
    "revoked": true
}
```

5a. List API Keys
```
curl --location 'http://localhost:8080/api_keys/merchant_1729063721/list' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin'
```

5b. Response
```json
[
    {
        "key_id": "dev_kKu6dWq8k3ZwWOfsS3fi",
        "merchant_id": "merchant_1729063721",
        "name": "API Key 1",
        "description": null,
        "prefix": "dev_6gvWcG7g",
        "created": "2024-10-16T07:32:48.698Z",
        "expiration": "2038-01-19T03:14:08.000Z"
    },
    {
        "key_id": "dev_f1PMFHmWHi2v0fmZp7t2",
        "merchant_id": "merchant_1729063721",
        "name": "API Key 2",
        "description": null,
        "prefix": "dev_jSU9qvIU",
        "created": "2024-10-16T07:32:54.057Z",
        "expiration": "2038-01-19T03:14:08.000Z"
    }
]
```
## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
