diff --git a/keyserver/src/responders/activity-responders.js b/keyserver/src/responders/activity-responders.js
--- a/keyserver/src/responders/activity-responders.js
+++ b/keyserver/src/responders/activity-responders.js
@@ -12,7 +12,7 @@
   setThreadUnreadStatusResult,
   updateActivityResultValidator,
 } from 'lib/types/activity-types.js';
-import { tShape } from 'lib/utils/validation-utils.js';
+import { tShape, tID } from 'lib/utils/validation-utils.js';
 
 import type { Viewer } from '../session/viewer.js';
 import {
@@ -24,8 +24,8 @@
 const activityUpdatesInputValidator: TList<Array<ActivityUpdate>> = t.list(
   tShape({
     focus: t.Bool,
-    threadID: t.String,
-    latestMessage: t.maybe(t.String),
+    threadID: tID,
+    latestMessage: t.maybe(tID),
   }),
 );
 
@@ -43,10 +43,10 @@
   return validateOutput(viewer, updateActivityResultValidator, result);
 }
 
-const setThreadUnreadStatusValidator = tShape({
-  threadID: t.String,
+const setThreadUnreadStatusValidator = tShape<SetThreadUnreadStatusRequest>({
+  threadID: tID,
   unread: t.Bool,
-  latestMessage: t.maybe(t.String),
+  latestMessage: t.maybe(tID),
 });
 async function threadSetUnreadStatusResponder(
   viewer: Viewer,
diff --git a/keyserver/src/responders/entry-responders.js b/keyserver/src/responders/entry-responders.js
--- a/keyserver/src/responders/entry-responders.js
+++ b/keyserver/src/responders/entry-responders.js
@@ -68,7 +68,7 @@
         }),
         tShape({
           type: tString(calendarThreadFilterTypes.THREAD_LIST),
-          threadIDs: t.list(t.String),
+          threadIDs: t.list(tID),
         }),
       ]),
     ),
@@ -85,7 +85,7 @@
       }),
       tShape({
         type: tString(calendarThreadFilterTypes.THREAD_LIST),
-        threadIDs: t.list(t.String),
+        threadIDs: t.list(tID),
       }),
     ]),
   ),
@@ -151,7 +151,7 @@
 }
 
 const entryRevisionHistoryFetchInputValidator = tShape({
-  id: t.String,
+  id: tID,
 });
 
 export const fetchEntryRevisionInfosResultValidator: TInterface<FetchEntryRevisionInfosResult> =
@@ -179,7 +179,7 @@
   sessionID: t.maybe(t.String),
   timestamp: t.Number,
   date: tDate,
-  threadID: t.String,
+  threadID: tID,
   localID: t.maybe(t.String),
   calendarQuery: t.maybe(newEntryQueryInputValidator),
 });
@@ -202,7 +202,7 @@
 }
 
 const saveEntryRequestInputValidator = tShape({
-  entryID: t.String,
+  entryID: tID,
   text: t.String,
   prevText: t.String,
   sessionID: t.maybe(t.String),
@@ -221,7 +221,7 @@
 }
 
 const deleteEntryRequestInputValidator = tShape({
-  entryID: t.String,
+  entryID: tID,
   prevText: t.String,
   sessionID: t.maybe(t.String),
   timestamp: t.Number,
@@ -246,7 +246,7 @@
 }
 
 const restoreEntryRequestInputValidator = tShape({
-  entryID: t.String,
+  entryID: tID,
   sessionID: t.maybe(t.String),
   timestamp: t.Number,
   calendarQuery: t.maybe(newEntryQueryInputValidator),
diff --git a/keyserver/src/responders/message-report-responder.js b/keyserver/src/responders/message-report-responder.js
--- a/keyserver/src/responders/message-report-responder.js
+++ b/keyserver/src/responders/message-report-responder.js
@@ -1,20 +1,20 @@
 // @flow
 
-import t, { type TInterface } from 'tcomb';
+import type { TInterface } from 'tcomb';
 
 import {
   type MessageReportCreationRequest,
   type MessageReportCreationResult,
 } from 'lib/types/message-report-types.js';
 import { rawMessageInfoValidator } from 'lib/types/message-types.js';
-import { tShape } from 'lib/utils/validation-utils.js';
+import { tShape, tID } from 'lib/utils/validation-utils.js';
 
 import createMessageReport from '../creators/message-report-creator.js';
 import type { Viewer } from '../session/viewer.js';
 import { validateInput, validateOutput } from '../utils/validation-utils.js';
 
 const messageReportCreationRequestInputValidator = tShape({
-  messageID: t.String,
+  messageID: tID,
 });
 
 export const messageReportCreationResultValidator: TInterface<MessageReportCreationResult> =
diff --git a/keyserver/src/responders/message-responders.js b/keyserver/src/responders/message-responders.js
--- a/keyserver/src/responders/message-responders.js
+++ b/keyserver/src/responders/message-responders.js
@@ -37,6 +37,7 @@
   tRegex,
   tShape,
   tMediaMessageMedia,
+  tID,
 } from 'lib/utils/validation-utils.js';
 
 import createMessages from '../creators/message-creator.js';
@@ -63,7 +64,7 @@
 import { validateInput, validateOutput } from '../utils/validation-utils.js';
 
 const sendTextMessageRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   localID: t.maybe(t.String),
   text: t.String,
   sidebarCreation: t.maybe(t.Boolean),
@@ -123,7 +124,7 @@
 }
 
 const fetchMessageInfosRequestInputValidator = tShape({
-  cursors: t.dict(t.String, t.maybe(t.String)),
+  cursors: t.dict(tID, t.maybe(tID)),
   numberPerThread: t.maybe(t.Number),
 });
 
@@ -154,13 +155,13 @@
 const sendMultimediaMessageRequestInputValidator = t.union([
   // This option is only used for messageTypes.IMAGES
   tShape({
-    threadID: t.String,
+    threadID: tID,
     localID: t.String,
     sidebarCreation: t.maybe(t.Boolean),
-    mediaIDs: t.list(t.String),
+    mediaIDs: t.list(tID),
   }),
   tShape({
-    threadID: t.String,
+    threadID: tID,
     localID: t.String,
     sidebarCreation: t.maybe(t.Boolean),
     mediaMessageContents: t.list(tMediaMessageMedia),
@@ -248,9 +249,9 @@
 }
 
 const sendReactionMessageRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   localID: t.maybe(t.String),
-  targetMessageID: t.String,
+  targetMessageID: tID,
   reaction: tRegex(onlyOneEmojiRegex),
   action: t.enums.of(['add_reaction', 'remove_reaction']),
 });
@@ -323,7 +324,7 @@
 }
 
 const editMessageRequestInputValidator = tShape({
-  targetMessageID: t.String,
+  targetMessageID: tID,
   text: t.String,
 });
 
@@ -414,7 +415,7 @@
 }
 
 const fetchPinnedMessagesResponderInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
 });
 
 export const fetchPinnedMessagesResultValidator: TInterface<FetchPinnedMessagesResult> =
diff --git a/keyserver/src/responders/responder-validators.test.js b/keyserver/src/responders/responder-validators.test.js
--- a/keyserver/src/responders/responder-validators.test.js
+++ b/keyserver/src/responders/responder-validators.test.js
@@ -32,6 +32,7 @@
   threadFetchMediaResultValidator,
   threadJoinResultValidator,
   toggleMessagePinResultValidator,
+  roleChangeRequestInputValidator,
 } from './thread-responders.js';
 import {
   logInResponseValidator,
@@ -815,6 +816,25 @@
       toggleMessagePinResultValidator.is({ ...response, threadID: undefined }),
     ).toBe(false);
   });
+
+  it('should validate role change request input', () => {
+    const input = {
+      threadID: '123',
+      memberIDs: [],
+      role: '1',
+    };
+
+    expect(roleChangeRequestInputValidator.is(input)).toBe(true);
+    expect(roleChangeRequestInputValidator.is({ ...input, role: '2|1' })).toBe(
+      true,
+    );
+    expect(roleChangeRequestInputValidator.is({ ...input, role: '-1' })).toBe(
+      false,
+    );
+    expect(roleChangeRequestInputValidator.is({ ...input, role: '2|-1' })).toBe(
+      false,
+    );
+  });
 });
 
 describe('message responders', () => {
diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js
--- a/keyserver/src/responders/thread-responders.js
+++ b/keyserver/src/responders/thread-responders.js
@@ -59,7 +59,7 @@
 import { validateInput, validateOutput } from '../utils/validation-utils.js';
 
 const threadDeletionRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   accountPassword: t.maybe(tPassword),
 });
 
@@ -81,14 +81,18 @@
   return validateOutput(viewer, leaveThreadResultValidator, result);
 }
 
-const roleChangeRequestInputValidator = tShape({
-  threadID: t.String,
-  memberIDs: t.list(t.String),
-  role: t.refinement(t.String, str => {
-    const int = parseInt(str, 10);
-    return String(int) === str && int > 0;
-  }),
-});
+export const roleChangeRequestInputValidator: TInterface<RoleChangeRequest> =
+  tShape<RoleChangeRequest>({
+    threadID: tID,
+    memberIDs: t.list(t.String),
+    role: t.refinement(tID, str => {
+      if (str.indexOf('|') !== -1) {
+        str = str.split('|')[1];
+      }
+      const int = parseInt(str, 10);
+      return String(int) === str && int > 0;
+    }),
+  });
 
 export const changeThreadSettingsResultValidator: TInterface<ChangeThreadSettingsResult> =
   tShape<ChangeThreadSettingsResult>({
@@ -111,7 +115,7 @@
 }
 
 const removeMembersRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   memberIDs: t.list(t.String),
 });
 
@@ -126,7 +130,7 @@
 }
 
 const leaveThreadRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
 });
 
 async function threadLeaveResponder(
@@ -140,13 +144,13 @@
 }
 
 const updateThreadRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   changes: tShape({
     type: t.maybe(tNumEnum(values(threadTypes))),
     name: t.maybe(t.String),
     description: t.maybe(t.String),
     color: t.maybe(tColor),
-    parentThreadID: t.maybe(t.String),
+    parentThreadID: t.maybe(tID),
     newMemberIDs: t.maybe(t.list(t.String)),
     avatar: t.maybe(updateUserAvatarRequestValidator),
   }),
@@ -167,14 +171,14 @@
   name: t.maybe(t.String),
   description: t.maybe(t.String),
   color: t.maybe(tColor),
-  parentThreadID: t.maybe(t.String),
+  parentThreadID: t.maybe(tID),
   initialMemberIDs: t.maybe(t.list(t.String)),
   calendarQuery: t.maybe(entryQueryInputValidator),
 };
 const newThreadRequestInputValidator: TUnion<ServerNewThreadRequest> = t.union([
   tShape({
     type: tNumEnum([threadTypes.SIDEBAR]),
-    sourceMessageID: t.String,
+    sourceMessageID: tID,
     ...threadRequestValidationShape,
   }),
   tShape({
@@ -213,7 +217,7 @@
 }
 
 const joinThreadRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   calendarQuery: t.maybe(entryQueryInputValidator),
   inviteLinkSecret: t.maybe(t.String),
 });
@@ -246,7 +250,7 @@
 }
 
 const threadFetchMediaRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   limit: t.Number,
   offset: t.Number,
 });
@@ -265,7 +269,7 @@
 }
 
 const toggleMessagePinRequestInputValidator = tShape({
-  messageID: t.String,
+  messageID: tID,
   action: t.enums.of(['pin', 'unpin']),
 });
 
diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js
--- a/keyserver/src/responders/user-responders.js
+++ b/keyserver/src/responders/user-responders.js
@@ -134,7 +134,7 @@
 import { validateInput, validateOutput } from '../utils/validation-utils.js';
 
 const subscriptionUpdateRequestInputValidator = tShape({
-  threadID: t.String,
+  threadID: tID,
   updatedFields: tShape({
     pushNotifs: t.maybe(t.Boolean),
     home: t.maybe(t.Boolean),
@@ -403,7 +403,7 @@
   username: t.maybe(t.String),
   usernameOrEmail: t.maybe(t.union([tEmail, tOldValidUsername])),
   password: tPassword,
-  watchedIDs: t.list(t.String),
+  watchedIDs: t.list(tID),
   calendarQuery: t.maybe(entryQueryInputValidator),
   deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator),
   platformDetails: tPlatformDetails,
@@ -514,7 +514,7 @@
   calendarQuery: entryQueryInputValidator,
   deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator),
   platformDetails: tPlatformDetails,
-  watchedIDs: t.list(t.String),
+  watchedIDs: t.list(tID),
   signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator),
 });
 
@@ -647,7 +647,7 @@
 const updatePasswordRequestInputValidator = tShape({
   code: t.String,
   password: tPassword,
-  watchedIDs: t.list(t.String),
+  watchedIDs: t.list(tID),
   calendarQuery: t.maybe(entryQueryInputValidator),
   deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator),
   platformDetails: tPlatformDetails,
diff --git a/keyserver/src/utils/validation-utils.js b/keyserver/src/utils/validation-utils.js
--- a/keyserver/src/utils/validation-utils.js
+++ b/keyserver/src/utils/validation-utils.js
@@ -231,7 +231,7 @@
     return assertWithValidator(converted, validator);
   }
 
-  if (validator.meta.kind === 'maybe') {
+  if (validator.meta.kind === 'maybe' || validator.meta.kind === 'subtype') {
     return convertObject(
       validator.meta.type,
       input,
diff --git a/keyserver/src/utils/validation-utils.test.js b/keyserver/src/utils/validation-utils.test.js
--- a/keyserver/src/utils/validation-utils.test.js
+++ b/keyserver/src/utils/validation-utils.test.js
@@ -106,4 +106,17 @@
       convertClientIDsToServerIDs('0', validator, clientData),
     ).toStrictEqual(serverData);
   });
+
+  it('should convert a refinement', () => {
+    const validator = t.refinement(tID, () => true);
+    const serverData = '1';
+    const clientData = '0|1';
+
+    expect(
+      convertServerIDsToClientIDs('0', validator, serverData),
+    ).toStrictEqual(clientData);
+    expect(
+      convertClientIDsToServerIDs('0', validator, clientData),
+    ).toStrictEqual(serverData);
+  });
 });
diff --git a/lib/utils/avatar-utils.js b/lib/utils/avatar-utils.js
--- a/lib/utils/avatar-utils.js
+++ b/lib/utils/avatar-utils.js
@@ -3,7 +3,7 @@
 import t from 'tcomb';
 import type { TUnion, TInterface } from 'tcomb';
 
-import { tRegex, tShape, tString } from './validation-utils.js';
+import { tRegex, tShape, tString, tID } from './validation-utils.js';
 import { validHexColorRegex } from '../shared/account-utils.js';
 import { onlyOneEmojiRegex } from '../shared/emojis.js';
 import type {
@@ -22,7 +22,7 @@
 
 const imageAvatarDBContentValidator: TInterface<ImageAvatarDBContent> = tShape({
   type: tString('image'),
-  uploadID: t.String,
+  uploadID: tID,
 });
 
 const ensAvatarDBContentValidator: TInterface<ENSAvatarDBContent> = tShape({
diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js
--- a/lib/utils/validation-utils.js
+++ b/lib/utils/validation-utils.js
@@ -82,13 +82,13 @@
 
 const tMediaMessagePhoto: TInterface<PhotoMessageServerDBContent> = tShape({
   type: tString('photo'),
-  uploadID: t.String,
+  uploadID: tID,
 });
 
 const tMediaMessageVideo: TInterface<VideoMessageServerDBContent> = tShape({
   type: tString('video'),
-  uploadID: t.String,
-  thumbnailUploadID: t.String,
+  uploadID: tID,
+  thumbnailUploadID: tID,
 });
 
 const tMediaMessageMedia: TUnion<MediaMessageServerDBContent> = t.union([