diff --git a/packages/client/src/components/MkChannelList.vue b/packages/client/src/components/MkChannelList.vue
index d6c9a44c4e..15f199c90f 100644
--- a/packages/client/src/components/MkChannelList.vue
+++ b/packages/client/src/components/MkChannelList.vue
@@ -24,13 +24,14 @@
 
 <script lang="ts" setup>
 import MkChannelPreview from "@/components/MkChannelPreview.vue";
-import type { Paging } from "@/components/MkPagination.vue";
+import type { PagingOf } from "@/components/MkPagination.vue";
 import MkPagination from "@/components/MkPagination.vue";
 import { i18n } from "@/i18n";
+import type { entities } from "firefish-js";
 
 const props = withDefaults(
 	defineProps<{
-		pagination: Paging;
+		pagination: PagingOf<entities.Channel>;
 		noGap?: boolean;
 		extractor?: (item: any) => any;
 	}>(),
diff --git a/packages/client/src/components/MkFileListForAdmin.vue b/packages/client/src/components/MkFileListForAdmin.vue
index 2889a74c81..d4cd8574aa 100644
--- a/packages/client/src/components/MkFileListForAdmin.vue
+++ b/packages/client/src/components/MkFileListForAdmin.vue
@@ -1,7 +1,7 @@
 <template>
 	<div>
 		<MkPagination
-			v-slot="{ items }"
+			v-slot="{ items }: { items: entities.DriveFile[]}"
 			:pagination="pagination"
 			class="urempief"
 			:class="{ grid: viewMode === 'grid' }"
@@ -53,13 +53,15 @@
 
 <script lang="ts" setup>
 import { acct } from "firefish-js";
+import type { entities } from "firefish-js";
 import MkPagination from "@/components/MkPagination.vue";
+import type { PagingOf } from "@/components/MkPagination.vue";
 import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
 import bytes from "@/filters/bytes";
 import { i18n } from "@/i18n";
 
 defineProps<{
-	pagination: any;
+	pagination: PagingOf<entities.DriveFile>;
 	viewMode: "grid" | "list";
 }>();
 </script>
diff --git a/packages/client/src/components/MkNotes.vue b/packages/client/src/components/MkNotes.vue
index 07ed2c3f12..2351cd6e47 100644
--- a/packages/client/src/components/MkNotes.vue
+++ b/packages/client/src/components/MkNotes.vue
@@ -40,17 +40,18 @@
 
 <script lang="ts" setup>
 import { ref } from "vue";
-import type { Paging } from "@/components/MkPagination.vue";
+import type { PagingOf } from "@/components/MkPagination.vue";
 import XNote from "@/components/MkNote.vue";
 import XList from "@/components/MkDateSeparatedList.vue";
 import MkPagination from "@/components/MkPagination.vue";
 import { i18n } from "@/i18n";
 import { scroll } from "@/scripts/scroll";
+import type { entities } from "firefish-js";
 
 const tlEl = ref<HTMLElement>();
 
 defineProps<{
-	pagination: Paging;
+	pagination: PagingOf<entities.Note>;
 	noGap?: boolean;
 	disableAutoLoad?: boolean;
 }>();
diff --git a/packages/client/src/components/MkNotifications.vue b/packages/client/src/components/MkNotifications.vue
index abb28d91b8..c8b54f4074 100644
--- a/packages/client/src/components/MkNotifications.vue
+++ b/packages/client/src/components/MkNotifications.vue
@@ -19,12 +19,8 @@
 				:no-gap="true"
 			>
 				<XNote
-					v-if="
-						['reply', 'quote', 'mention'].includes(
-							notification.type,
-						)
-					"
-					:key="notification.id"
+					v-if="isNoteNotification(notification)"
+					:key="'nn-' + notification.id"
 					:note="notification.note"
 					:collapsed-reply="
 						notification.type === 'reply' ||
@@ -34,7 +30,7 @@
 				/>
 				<XNotification
 					v-else
-					:key="notification.id"
+					:key="'n-' + notification.id"
 					:notification="notification"
 					:with-time="true"
 					:full="true"
@@ -47,8 +43,7 @@
 
 <script lang="ts" setup>
 import { computed, onMounted, onUnmounted, ref } from "vue";
-import type { notificationTypes } from "firefish-js";
-import type { Paging } from "@/components/MkPagination.vue";
+import type { entities, notificationTypes } from "firefish-js";
 import MkPagination from "@/components/MkPagination.vue";
 import XNotification from "@/components/MkNotification.vue";
 import XList from "@/components/MkDateSeparatedList.vue";
@@ -66,20 +61,29 @@ const stream = useStream();
 
 const pagingComponent = ref<InstanceType<typeof MkPagination>>();
 
-const pagination: Paging = {
+const pagination = {
 	endpoint: "i/notifications" as const,
 	limit: 10,
 	params: computed(() => ({
 		includeTypes: props.includeTypes ?? undefined,
-		excludeTypes: props.includeTypes ? undefined : me.mutingNotificationTypes,
+		excludeTypes: props.includeTypes ? undefined : me?.mutingNotificationTypes,
 		unreadOnly: props.unreadOnly,
 	})),
 };
 
-const onNotification = (notification) => {
+function isNoteNotification(
+	n: entities.Notification,
+): n is
+	| entities.ReplyNotification
+	| entities.QuoteNotification
+	| entities.MentionNotification {
+	return n.type === "reply" || n.type === "quote" || n.type === "mention";
+}
+
+const onNotification = (notification: entities.Notification) => {
 	const isMuted = props.includeTypes
 		? !props.includeTypes.includes(notification.type)
-		: me.mutingNotificationTypes.includes(notification.type);
+		: me?.mutingNotificationTypes.includes(notification.type);
 	if (isMuted || document.visibilityState === "visible") {
 		stream.send("readNotification", {
 			id: notification.id,
diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue
index f9a9e8a288..aee1fffbe3 100644
--- a/packages/client/src/components/MkPagination.vue
+++ b/packages/client/src/components/MkPagination.vue
@@ -66,10 +66,10 @@
 	</transition>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup generic="E extends keyof Endpoints">
 import type { ComputedRef } from "vue";
 import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
-import type { Endpoints } from "firefish-js";
+import type { Endpoints, TypeUtils } from "firefish-js";
 import * as os from "@/os";
 import {
 	getScrollContainer,
@@ -100,11 +100,13 @@ export interface Paging<E extends keyof Endpoints = keyof Endpoints> {
 	offsetMode?: boolean;
 }
 
+export type PagingOf<T> = Paging<TypeUtils.EndpointsOf<T[]>>;
+
 const SECOND_FETCH_LIMIT = 30;
 
 const props = withDefaults(
 	defineProps<{
-		pagination: Paging;
+		pagination: Paging<E>;
 		disableAutoLoad?: boolean;
 		displayLimit?: number;
 	}>(),
@@ -113,14 +115,17 @@ const props = withDefaults(
 	},
 );
 
+const slots = defineSlots<{
+	default(props: { items: Item[] }): unknown;
+	empty(props: null): never;
+}>();
+
 const emit = defineEmits<{
 	(ev: "queue", count: number): void;
 	(ev: "status", error: boolean): void;
 }>();
 
-type Item = Endpoints[typeof props.pagination.endpoint]["res"] & {
-	id: string;
-};
+type Item = Endpoints[E]["res"][number];
 
 const rootEl = ref<HTMLElement>();
 const items = ref<Item[]>([]);
diff --git a/packages/client/src/components/MkTimeline.vue b/packages/client/src/components/MkTimeline.vue
index 216723d30e..456a481fa2 100644
--- a/packages/client/src/components/MkTimeline.vue
+++ b/packages/client/src/components/MkTimeline.vue
@@ -44,7 +44,7 @@
 
 <script lang="ts" setup>
 import { computed, onUnmounted, provide, ref } from "vue";
-import type { Endpoints } from "firefish-js";
+import type { entities } from "firefish-js";
 import MkPullToRefresh from "@/components/MkPullToRefresh.vue";
 import XNotes from "@/components/MkNotes.vue";
 import MkInfo from "@/components/MkInfo.vue";
@@ -54,10 +54,23 @@ import { isSignedIn, me } from "@/me";
 import { i18n } from "@/i18n";
 import { defaultStore } from "@/store";
 import icon from "@/scripts/icon";
-import type { Paging } from "@/components/MkPagination.vue";
+import type { EndpointsOf } from "@/components/MkPagination.vue";
+
+export type TimelineSource =
+	| "antenna"
+	| "home"
+	| "local"
+	| "recommended"
+	| "social"
+	| "global"
+	| "mentions"
+	| "directs"
+	| "list"
+	| "channel"
+	| "file";
 
 const props = defineProps<{
-	src: string;
+	src: TimelineSource;
 	list?: string;
 	antenna?: string;
 	channel?: string;
@@ -73,7 +86,7 @@ const emit = defineEmits<{
 const tlComponent = ref<InstanceType<typeof XNotes>>();
 const pullToRefreshComponent = ref<InstanceType<typeof MkPullToRefresh>>();
 
-let endpoint = ""; // keyof Endpoints
+let endpoint: EndpointsOf<entities.Note[]>; // keyof Endpoints
 let query: {
 	antennaId?: string | undefined;
 	withReplies?: boolean;
@@ -81,7 +94,9 @@ let query: {
 	listId?: string | undefined;
 	channelId?: string | undefined;
 	fileId?: string | undefined;
-};
+} = {};
+
+// FIXME: The type defination is wrong here, need fix
 let connection: {
 	on: (
 		arg0: string,
@@ -96,14 +111,14 @@ let tlHintClosed: boolean;
 let tlNotesCount = 0;
 const queue = ref(0);
 
-const prepend = (note) => {
+const prepend = (note: entities.Note) => {
 	tlNotesCount++;
 	tlComponent.value?.pagingComponent?.prepend(note);
 
 	emit("note");
 
 	if (props.sound) {
-		sound.play(isSignedIn && note.userId === me.id ? "noteMy" : "note");
+		sound.play(isSignedIn && note.userId === me?.id ? "noteMy" : "note");
 	}
 };
 
@@ -169,14 +184,17 @@ if (props.src === "antenna") {
 	query = {
 		fileId: props.fileId,
 	};
+} else {
+	throw "NoEndpointError";
 }
 
 const stream = useStream();
 
 function connectChannel() {
 	if (props.src === "antenna") {
+		if (!props.antenna) throw "NoAntennaProvided";
 		connection = stream.useChannel("antenna", {
-			antennaId: props.antenna!,
+			antennaId: props.antenna,
 		});
 	} else if (props.src === "home") {
 		connection = stream.useChannel("homeTimeline", {
@@ -265,8 +283,8 @@ function reloadTimeline() {
 	});
 }
 
-const pagination: Paging = {
-	endpoint: endpoint as keyof Endpoints,
+const pagination = {
+	endpoint,
 	limit: 10,
 	params: query,
 };
diff --git a/packages/client/src/pages/about.federation.vue b/packages/client/src/pages/about.federation.vue
index 06a9db2f50..71b4f46de7 100644
--- a/packages/client/src/pages/about.federation.vue
+++ b/packages/client/src/pages/about.federation.vue
@@ -113,10 +113,11 @@ import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
 import FormSplit from "@/components/form/split.vue";
 import { i18n } from "@/i18n";
 import icon from "@/scripts/icon";
+import type { instanceSortParam } from "firefish-js";
 
 const host = ref("");
 const state = ref("federating");
-const sort = ref("+pubSub");
+const sort = ref<(typeof instanceSortParam)[number]>("+pubSub");
 const pagination = {
 	endpoint: "federation/instances" as const,
 	limit: 10,
diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue
index ce4fc1f58d..c4d9e6ec40 100644
--- a/packages/client/src/pages/admin/files.vue
+++ b/packages/client/src/pages/admin/files.vue
@@ -94,7 +94,7 @@ const origin = ref("local");
 const type = ref(null);
 const searchHost = ref("");
 const userId = ref("");
-const viewMode = ref("grid");
+const viewMode = ref<"list" | "grid">("grid");
 const pagination = {
 	endpoint: "admin/drive/files" as const,
 	limit: 10,
diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index e56704879c..a065120645 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -313,7 +313,7 @@ const isSilenced = ref(false);
 const faviconUrl = ref<string | null>(null);
 
 const usersPagination = {
-	endpoint: isAdmin ? "admin/show-users" : ("users" as const),
+	endpoint: isAdmin ? ("admin/show-users" as const) : ("users" as const),
 	limit: 10,
 	params: {
 		sort: "+updatedAt",
diff --git a/packages/client/src/pages/note-history.vue b/packages/client/src/pages/note-history.vue
index aaf8519728..fdf8178fbf 100644
--- a/packages/client/src/pages/note-history.vue
+++ b/packages/client/src/pages/note-history.vue
@@ -8,12 +8,12 @@
 			<MkPagination
 				v-else
 				ref="pagingComponent"
-				v-slot="{ items }: { items: entities.NoteEdit[] }"
+				v-slot="{ items }"
 				:pagination="pagination"
 			>
 				<div ref="tlEl" class="giivymft noGap">
 					<XList
-						v-slot="{ item }: { item: entities.Note }"
+						v-slot="{ item }"
 						:items="convertNoteEditsToNotes(items)"
 						class="notes"
 						:no-gap="true"
@@ -35,7 +35,6 @@
 <script lang="ts" setup>
 import { computed, onMounted, ref } from "vue";
 import MkPagination from "@/components/MkPagination.vue";
-import type { Paging } from "@/components/MkPagination.vue";
 import { api } from "@/os";
 import XList from "@/components/MkDateSeparatedList.vue";
 import XNote from "@/components/MkNote.vue";
@@ -50,7 +49,7 @@ const props = defineProps<{
 	noteId: string;
 }>();
 
-const pagination: Paging = {
+const pagination = {
 	endpoint: "notes/history" as const,
 	limit: 10,
 	offsetMode: true,
diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts
index d5002c086d..d3564573f0 100644
--- a/packages/firefish-js/src/api.types.ts
+++ b/packages/firefish-js/src/api.types.ts
@@ -38,6 +38,8 @@ import type {
 	UserSorting,
 } from "./entities";
 
+import type * as consts from "./consts";
+
 type TODO = Record<string, any> | null;
 
 type NoParams = Record<string, never>;
@@ -84,7 +86,7 @@ export type Endpoints = {
 	"admin/server-info": { req: TODO; res: TODO };
 	"admin/show-moderation-logs": { req: TODO; res: TODO };
 	"admin/show-user": { req: TODO; res: TODO };
-	"admin/show-users": { req: TODO; res: TODO };
+	"admin/show-users": { req: TODO; res: User[] };
 	"admin/silence-user": { req: TODO; res: TODO };
 	"admin/suspend-user": { req: TODO; res: TODO };
 	"admin/unsilence-user": { req: TODO; res: TODO };
@@ -101,7 +103,18 @@ export type Endpoints = {
 	"admin/announcements/update": { req: TODO; res: TODO };
 	"admin/drive/clean-remote-files": { req: TODO; res: TODO };
 	"admin/drive/cleanup": { req: TODO; res: TODO };
-	"admin/drive/files": { req: TODO; res: TODO };
+	"admin/drive/files": {
+		req: {
+			limit?: number;
+			sinceId?: DriveFile["id"];
+			untilId?: DriveFile["id"];
+			userId?: User["id"];
+			type?: string;
+			origin?: "combined" | "local" | "remote";
+			hostname?: string;
+		};
+		res: DriveFile[];
+	};
 	"admin/drive/show-file": { req: TODO; res: TODO };
 	"admin/emoji/add": { req: TODO; res: TODO };
 	"admin/emoji/copy": { req: TODO; res: TODO };
@@ -200,7 +213,7 @@ export type Endpoints = {
 	"channels/owned": { req: TODO; res: TODO };
 	"channels/pin-note": { req: TODO; res: TODO };
 	"channels/show": { req: TODO; res: TODO };
-	"channels/timeline": { req: TODO; res: TODO };
+	"channels/timeline": { req: TODO; res: Note[] };
 	"channels/unfollow": { req: TODO; res: TODO };
 	"channels/update": { req: TODO; res: TODO };
 
@@ -238,7 +251,7 @@ export type Endpoints = {
 		};
 		res: DriveFile[];
 	};
-	"drive/files/attached-notes": { req: TODO; res: TODO };
+	"drive/files/attached-notes": { req: TODO; res: Note[] };
 	"drive/files/check-existence": { req: TODO; res: TODO };
 	"drive/files/create": { req: TODO; res: TODO };
 	"drive/files/delete": { req: { fileId: DriveFile["id"] }; res: null };
@@ -360,25 +373,7 @@ export type Endpoints = {
 			publishing?: boolean | null;
 			limit?: number;
 			offset?: number;
-			sort?:
-				| "+pubSub"
-				| "-pubSub"
-				| "+notes"
-				| "-notes"
-				| "+users"
-				| "-users"
-				| "+following"
-				| "-following"
-				| "+followers"
-				| "-followers"
-				| "+caughtAt"
-				| "-caughtAt"
-				| "+lastCommunicatedAt"
-				| "-lastCommunicatedAt"
-				| "+driveUsage"
-				| "-driveUsage"
-				| "+driveFiles"
-				| "-driveFiles";
+			sort?: (typeof consts.instanceSortParam)[number];
 		};
 		res: Instance[];
 	};
diff --git a/packages/firefish-js/src/consts.ts b/packages/firefish-js/src/consts.ts
index 16328ceb84..0684d6c08f 100644
--- a/packages/firefish-js/src/consts.ts
+++ b/packages/firefish-js/src/consts.ts
@@ -151,3 +151,24 @@ export const languages = [
 	"yi",
 	"zh",
 ] as const;
+
+export const instanceSortParam = [
+	"+pubSub",
+	"-pubSub",
+	"+notes",
+	"-notes",
+	"+users",
+	"-users",
+	"+following",
+	"-following",
+	"+followers",
+	"-followers",
+	"+caughtAt",
+	"-caughtAt",
+	"+lastCommunicatedAt",
+	"-lastCommunicatedAt",
+	"+driveUsage",
+	"-driveUsage",
+	"+driveFiles",
+	"-driveFiles",
+] as const;
diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts
index ef1804ee71..c5f09fb2d6 100644
--- a/packages/firefish-js/src/entities.ts
+++ b/packages/firefish-js/src/entities.ts
@@ -1,3 +1,5 @@
+import type * as consts from "./consts";
+
 export type ID = string;
 export type DateString = string;
 
@@ -108,7 +110,7 @@ export type MeDetailed = UserDetailed & {
 	isExplorable: boolean;
 	mutedWords: string[][];
 	mutedPatterns: string[];
-	mutingNotificationTypes: string[];
+	mutingNotificationTypes: (typeof consts.notificationTypes)[number][];
 	noCrawle: boolean;
 	preventAiLearning: boolean;
 	receiveAnnouncementEmail: boolean;
@@ -129,6 +131,8 @@ export type DriveFile = {
 	blurhash: string;
 	comment: string | null;
 	properties: Record<string, any>;
+	userId?: User["id"];
+	user?: User;
 };
 
 export type DriveFolder = TODO;
@@ -152,7 +156,8 @@ export type Note = {
 	visibleUserIds?: User["id"][];
 	lang?: string;
 	localOnly?: boolean;
-	channel?: Channel["id"];
+	channelId?: Channel["id"];
+	channel?: Channel;
 	myReaction?: string;
 	reactions: Record<string, number>;
 	renoteCount: number;
@@ -199,82 +204,98 @@ export type NoteReaction = {
 	type: string;
 };
 
-export type Notification = {
+interface BaseNotification {
 	id: ID;
 	createdAt: DateString;
 	isRead: boolean;
-} & (
-	| {
-			type: "reaction";
-			reaction: string;
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "reply";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "renote";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "quote";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "mention";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "pollVote";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "pollEnded";
-			user: User;
-			userId: User["id"];
-			note: Note;
-	  }
-	| {
-			type: "follow";
-			user: User;
-			userId: User["id"];
-	  }
-	| {
-			type: "followRequestAccepted";
-			user: User;
-			userId: User["id"];
-	  }
-	| {
-			type: "receiveFollowRequest";
-			user: User;
-			userId: User["id"];
-	  }
-	| {
-			type: "groupInvited";
-			invitation: UserGroup;
-			user: User;
-			userId: User["id"];
-	  }
-	| {
-			type: "app";
-			header?: string | null;
-			body: string;
-			icon?: string | null;
-	  }
-);
+	type: (typeof consts.notificationTypes)[number];
+}
+
+export interface ReactionNotification extends BaseNotification {
+	type: "reaction";
+	reaction: string;
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface ReplyNotification extends BaseNotification {
+	type: "reply";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface RenoteNotification extends BaseNotification {
+	type: "renote";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface QuoteNotification extends BaseNotification {
+	type: "quote";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface MentionNotification extends BaseNotification {
+	type: "mention";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface PollVoteNotification extends BaseNotification {
+	type: "pollVote";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface PollEndedNotification extends BaseNotification {
+	type: "pollEnded";
+	user: User;
+	userId: User["id"];
+	note: Note;
+}
+export interface FollowNotification extends BaseNotification {
+	type: "follow";
+	user: User;
+	userId: User["id"];
+}
+
+export interface FollowRequestAcceptedNotification extends BaseNotification {
+	type: "followRequestAccepted";
+	user: User;
+	userId: User["id"];
+}
+export interface ReceiveFollowRequestNotification extends BaseNotification {
+	type: "receiveFollowRequest";
+	user: User;
+	userId: User["id"];
+}
+export interface GroupInvitedNotification extends BaseNotification {
+	type: "groupInvited";
+	invitation: UserGroup;
+	user: User;
+	userId: User["id"];
+}
+export interface AppNotification extends BaseNotification {
+	type: "app";
+	header?: string | null;
+	body: string;
+	icon?: string | null;
+}
+
+export type Notification =
+	| ReactionNotification
+	| ReplyNotification
+	| RenoteNotification
+	| QuoteNotification
+	| MentionNotification
+	| PollVoteNotification
+	| PollEndedNotification
+	| FollowNotification
+	| FollowRequestAcceptedNotification
+	| ReceiveFollowRequestNotification
+	| GroupInvitedNotification
+	| AppNotification;
 
 export type MessagingMessage = {
 	id: ID;
@@ -451,6 +472,7 @@ export type FollowRequest = {
 
 export type Channel = {
 	id: ID;
+	name: string;
 	// TODO
 };
 
diff --git a/packages/firefish-js/src/index.ts b/packages/firefish-js/src/index.ts
index cb08fad8ab..6639985481 100644
--- a/packages/firefish-js/src/index.ts
+++ b/packages/firefish-js/src/index.ts
@@ -4,6 +4,7 @@ import { Endpoints } from "./api.types";
 import * as consts from "./consts";
 import Stream, { Connection } from "./streaming";
 import * as StreamTypes from "./streaming.types";
+import type * as TypeUtils from "./type-utils";
 
 export {
 	Endpoints,
@@ -12,6 +13,7 @@ export {
 	StreamTypes,
 	acct,
 	type Acct,
+	type TypeUtils,
 };
 
 export const permissions = consts.permissions;
@@ -20,6 +22,7 @@ export const noteVisibilities = consts.noteVisibilities;
 export const mutedNoteReasons = consts.mutedNoteReasons;
 export const languages = consts.languages;
 export const ffVisibility = consts.ffVisibility;
+export const instanceSortParam = consts.instanceSortParam;
 
 // api extractor not supported yet
 //export * as api from './api';
diff --git a/packages/firefish-js/src/streaming.types.ts b/packages/firefish-js/src/streaming.types.ts
index c0b0b030cd..e9be8622af 100644
--- a/packages/firefish-js/src/streaming.types.ts
+++ b/packages/firefish-js/src/streaming.types.ts
@@ -13,6 +13,10 @@ import type {
 
 type FIXME = any;
 
+type TimelineParams = {
+	withReplies?: boolean;
+};
+
 export type Channels = {
 	main: {
 		params: null;
@@ -56,35 +60,35 @@ export type Channels = {
 		receives: null;
 	};
 	homeTimeline: {
-		params: null;
+		params?: TimelineParams;
 		events: {
 			note: (payload: Note) => void;
 		};
 		receives: null;
 	};
 	localTimeline: {
-		params: null;
+		params: TimelineParams;
 		events: {
 			note: (payload: Note) => void;
 		};
 		receives: null;
 	};
 	hybridTimeline: {
-		params: null;
+		params: TimelineParams;
 		events: {
 			note: (payload: Note) => void;
 		};
 		receives: null;
 	};
 	recommendedTimeline: {
-		params: null;
+		params: TimelineParams;
 		events: {
 			note: (payload: Note) => void;
 		};
 		receives: null;
 	};
 	globalTimeline: {
-		params: null;
+		params: TimelineParams;
 		events: {
 			note: (payload: Note) => void;
 		};
diff --git a/packages/firefish-js/src/type-utils.ts b/packages/firefish-js/src/type-utils.ts
new file mode 100644
index 0000000000..6c1a67ac59
--- /dev/null
+++ b/packages/firefish-js/src/type-utils.ts
@@ -0,0 +1,13 @@
+import type { Endpoints } from "./api.types";
+
+export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
+	T,
+>() => T extends Y ? 1 : 2
+	? true
+	: false;
+
+export type PropertyOfType<Type, U> = {
+	[K in keyof Type]: Type[K] extends U ? K : never;
+}[keyof Type];
+
+export type EndpointsOf<T> = PropertyOfType<Endpoints, { res: T }>;