From 510de87607283d4d49239b1d14026a577299c99e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 19 Feb 2022 14:05:32 +0900
Subject: [PATCH] refactor: use ajv instead of cafy (#8324)

* wip

* wip

* Update abuse-user-reports.ts

* Update files.ts

* Update list-remote.ts

* Update list.ts

* Update show-users.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update update.ts

* Update search.ts

* Update reactions.ts

* Update search.ts

* wip

* wip

* wip

* wip

* Update update.ts

* Update relation.ts

* Update available.ts

* wip

* wip

* wip

* Update packages/backend/src/server/api/define.ts

Co-authored-by: Johann150 <johann.galle@protonmail.com>

* Update define.ts

* Update define.ts

* typo

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update update.ts

* wip

* Update signup.ts

* Update call.ts

* minimum for limit

* type

* remove needless annotation

* wip

* Update signup.ts

* wip

* wip

* fix

* Update create.ts

Co-authored-by: Johann150 <johann.galle@protonmail.com>
---
 packages/backend/package.json                 |   1 +
 packages/backend/src/misc/schema.ts           |  80 +-
 .../src/models/repositories/drive-folder.ts   |   7 -
 .../models/repositories/messaging-message.ts  |   4 -
 .../backend/src/models/repositories/note.ts   |   4 -
 .../backend/src/models/repositories/user.ts   |  29 +-
 .../backend/src/server/api/api-handler.ts     |   2 +-
 packages/backend/src/server/api/call.ts       |  16 +-
 .../backend/src/server/api/common/signup.ts   |   4 +-
 packages/backend/src/server/api/define.ts     |  69 +-
 packages/backend/src/server/api/endpoints.ts  |  18 +-
 .../api/endpoints/admin/abuse-user-reports.ts |  63 +-
 .../api/endpoints/admin/accounts/create.ts    |  21 +-
 .../api/endpoints/admin/accounts/delete.ts    |  14 +-
 .../server/api/endpoints/admin/ad/create.ts   |  37 +-
 .../server/api/endpoints/admin/ad/delete.ts   |  18 +-
 .../src/server/api/endpoints/admin/ad/list.ts |  27 +-
 .../server/api/endpoints/admin/ad/update.ts   |  46 +-
 .../endpoints/admin/announcements/create.ts   |  25 +-
 .../endpoints/admin/announcements/delete.ts   |  18 +-
 .../api/endpoints/admin/announcements/list.ts |  31 +-
 .../endpoints/admin/announcements/update.ts   |  30 +-
 .../admin/delete-all-files-of-a-user.ts       |  14 +-
 .../admin/drive/clean-remote-files.ts         |   8 +-
 .../api/endpoints/admin/drive/cleanup.ts      |   8 +-
 .../server/api/endpoints/admin/drive/files.ts |  52 +-
 .../api/endpoints/admin/drive/show-file.ts    |  23 +-
 .../endpoints/admin/emoji/add-aliases-bulk.ts |  23 +-
 .../server/api/endpoints/admin/emoji/add.ts   |  18 +-
 .../server/api/endpoints/admin/emoji/copy.ts  |  18 +-
 .../api/endpoints/admin/emoji/delete-bulk.ts  |  16 +-
 .../api/endpoints/admin/emoji/delete.ts       |  18 +-
 .../api/endpoints/admin/emoji/import-zip.ts   |  15 +-
 .../api/endpoints/admin/emoji/list-remote.ts  |  43 +-
 .../server/api/endpoints/admin/emoji/list.ts  |  41 +-
 .../admin/emoji/remove-aliases-bulk.ts        |  23 +-
 .../endpoints/admin/emoji/set-aliases-bulk.ts |  23 +-
 .../admin/emoji/set-category-bulk.ts          |  21 +-
 .../api/endpoints/admin/emoji/update.ts       |  35 +-
 .../admin/federation/delete-all-files.ts      |  13 +-
 .../refresh-remote-instance-metadata.ts       |  13 +-
 .../admin/federation/remove-all-following.ts  |  13 +-
 .../admin/federation/update-instance.ts       |  18 +-
 .../api/endpoints/admin/get-index-stats.ts    |   9 +-
 .../api/endpoints/admin/get-table-stats.ts    |  11 +-
 .../src/server/api/endpoints/admin/invite.ts  |  10 +-
 .../api/endpoints/admin/moderators/add.ts     |  14 +-
 .../api/endpoints/admin/moderators/remove.ts  |  14 +-
 .../api/endpoints/admin/promo/create.ts       |  23 +-
 .../server/api/endpoints/admin/queue/clear.ts |   8 +-
 .../endpoints/admin/queue/deliver-delayed.ts  |  11 +-
 .../endpoints/admin/queue/inbox-delayed.ts    |  11 +-
 .../server/api/endpoints/admin/queue/stats.ts |  10 +-
 .../server/api/endpoints/admin/relays/add.ts  |  17 +-
 .../server/api/endpoints/admin/relays/list.ts |  11 +-
 .../api/endpoints/admin/relays/remove.ts      |  13 +-
 .../api/endpoints/admin/reset-password.ts     |  18 +-
 .../admin/resolve-abuse-user-report.ts        |  21 +-
 .../server/api/endpoints/admin/send-email.ts  |  21 +-
 .../server/api/endpoints/admin/server-info.ts |  11 +-
 .../endpoints/admin/show-moderation-logs.ts   |  31 +-
 .../server/api/endpoints/admin/show-user.ts   |  18 +-
 .../server/api/endpoints/admin/show-users.ts  |  74 +-
 .../api/endpoints/admin/silence-user.ts       |  14 +-
 .../api/endpoints/admin/suspend-user.ts       |  14 +-
 .../api/endpoints/admin/unsilence-user.ts     |  14 +-
 .../api/endpoints/admin/unsuspend-user.ts     |  14 +-
 .../server/api/endpoints/admin/update-meta.ts | 381 ++-----
 .../src/server/api/endpoints/admin/vacuum.ts  |  17 +-
 .../src/server/api/endpoints/announcements.ts |  37 +-
 .../server/api/endpoints/antennas/create.ts   |  78 +-
 .../server/api/endpoints/antennas/delete.ts   |  18 +-
 .../src/server/api/endpoints/antennas/list.ts |   8 +-
 .../server/api/endpoints/antennas/notes.ts    |  46 +-
 .../src/server/api/endpoints/antennas/show.ts |  18 +-
 .../server/api/endpoints/antennas/update.ts   |  83 +-
 .../src/server/api/endpoints/ap/get.ts        |  17 +-
 .../src/server/api/endpoints/ap/show.ts       |  17 +-
 .../src/server/api/endpoints/app/create.ts    |  36 +-
 .../src/server/api/endpoints/app/show.ts      |  18 +-
 .../src/server/api/endpoints/auth/accept.ts   |  17 +-
 .../api/endpoints/auth/session/generate.ts    |  17 +-
 .../server/api/endpoints/auth/session/show.ts |  17 +-
 .../api/endpoints/auth/session/userkey.ts     |  22 +-
 .../server/api/endpoints/blocking/create.ts   |  18 +-
 .../server/api/endpoints/blocking/delete.ts   |  18 +-
 .../src/server/api/endpoints/blocking/list.ts |  31 +-
 .../server/api/endpoints/channels/create.ts   |  28 +-
 .../server/api/endpoints/channels/featured.ts |   8 +-
 .../server/api/endpoints/channels/follow.ts   |  18 +-
 .../server/api/endpoints/channels/followed.ts |  31 +-
 .../server/api/endpoints/channels/owned.ts    |  31 +-
 .../src/server/api/endpoints/channels/show.ts |  18 +-
 .../server/api/endpoints/channels/timeline.ts |  46 +-
 .../server/api/endpoints/channels/unfollow.ts |  18 +-
 .../server/api/endpoints/channels/update.ts   |  33 +-
 .../api/endpoints/charts/active-users.ts      |  31 +-
 .../server/api/endpoints/charts/ap-request.ts |  31 +-
 .../src/server/api/endpoints/charts/drive.ts  |  31 +-
 .../server/api/endpoints/charts/federation.ts |  31 +-
 .../server/api/endpoints/charts/hashtag.ts    |  36 +-
 .../server/api/endpoints/charts/instance.ts   |  36 +-
 .../src/server/api/endpoints/charts/notes.ts  |  31 +-
 .../server/api/endpoints/charts/user/drive.ts |  37 +-
 .../api/endpoints/charts/user/following.ts    |  37 +-
 .../server/api/endpoints/charts/user/notes.ts |  37 +-
 .../api/endpoints/charts/user/reactions.ts    |  37 +-
 .../src/server/api/endpoints/charts/users.ts  |  31 +-
 .../server/api/endpoints/clips/add-note.ts    |  23 +-
 .../src/server/api/endpoints/clips/create.ts  |  27 +-
 .../src/server/api/endpoints/clips/delete.ts  |  18 +-
 .../src/server/api/endpoints/clips/list.ts    |   8 +-
 .../src/server/api/endpoints/clips/notes.ts   |  36 +-
 .../src/server/api/endpoints/clips/show.ts    |  18 +-
 .../src/server/api/endpoints/clips/update.ts  |  33 +-
 .../backend/src/server/api/endpoints/drive.ts |   8 +-
 .../src/server/api/endpoints/drive/files.ts   |  42 +-
 .../endpoints/drive/files/attached-notes.ts   |  18 +-
 .../endpoints/drive/files/check-existence.ts  |  17 +-
 .../api/endpoints/drive/files/create.ts       |  43 +-
 .../api/endpoints/drive/files/delete.ts       |  18 +-
 .../api/endpoints/drive/files/find-by-hash.ts |  17 +-
 .../server/api/endpoints/drive/files/find.ts  |  24 +-
 .../server/api/endpoints/drive/files/show.ts  |  23 +-
 .../api/endpoints/drive/files/update.ts       |  51 +-
 .../endpoints/drive/files/upload-from-url.ts  |  45 +-
 .../src/server/api/endpoints/drive/folders.ts |  37 +-
 .../api/endpoints/drive/folders/create.ts     |  24 +-
 .../api/endpoints/drive/folders/delete.ts     |  18 +-
 .../api/endpoints/drive/folders/find.ts       |  24 +-
 .../api/endpoints/drive/folders/show.ts       |  18 +-
 .../api/endpoints/drive/folders/update.ts     |  28 +-
 .../src/server/api/endpoints/drive/stream.ts  |  36 +-
 .../api/endpoints/email-address/available.ts  |  17 +-
 .../src/server/api/endpoints/endpoint.ts      |  13 +-
 .../src/server/api/endpoints/endpoints.ts     |  11 +-
 .../api/endpoints/export-custom-emojis.ts     |   9 +-
 .../api/endpoints/federation/followers.ts     |  36 +-
 .../api/endpoints/federation/following.ts     |  36 +-
 .../api/endpoints/federation/instances.ts     |  68 +-
 .../api/endpoints/federation/show-instance.ts |  28 +-
 .../federation/update-remote-user.ts          |  14 +-
 .../server/api/endpoints/federation/users.ts  |  36 +-
 .../server/api/endpoints/following/create.ts  |  18 +-
 .../server/api/endpoints/following/delete.ts  |  18 +-
 .../api/endpoints/following/invalidate.ts     |  18 +-
 .../endpoints/following/requests/accept.ts    |  18 +-
 .../endpoints/following/requests/cancel.ts    |  18 +-
 .../api/endpoints/following/requests/list.ts  |   8 +-
 .../endpoints/following/requests/reject.ts    |  18 +-
 .../server/api/endpoints/gallery/featured.ts  |   8 +-
 .../server/api/endpoints/gallery/popular.ts   |   8 +-
 .../src/server/api/endpoints/gallery/posts.ts |  31 +-
 .../api/endpoints/gallery/posts/create.ts     |  36 +-
 .../api/endpoints/gallery/posts/delete.ts     |  18 +-
 .../api/endpoints/gallery/posts/like.ts       |  18 +-
 .../api/endpoints/gallery/posts/show.ts       |  18 +-
 .../api/endpoints/gallery/posts/unlike.ts     |  18 +-
 .../api/endpoints/gallery/posts/update.ts     |  41 +-
 .../api/endpoints/get-online-users-count.ts   |   9 +-
 .../src/server/api/endpoints/hashtags/list.ts |  56 +-
 .../server/api/endpoints/hashtags/search.ts   |  31 +-
 .../src/server/api/endpoints/hashtags/show.ts |  17 +-
 .../server/api/endpoints/hashtags/trend.ts    |   8 +-
 .../server/api/endpoints/hashtags/users.ts    |  56 +-
 .../backend/src/server/api/endpoints/i.ts     |  10 +-
 .../src/server/api/endpoints/i/2fa/done.ts    |  13 +-
 .../server/api/endpoints/i/2fa/key-done.ts    |  34 +-
 .../api/endpoints/i/2fa/password-less.ts      |  13 +-
 .../api/endpoints/i/2fa/register-key.ts       |  13 +-
 .../server/api/endpoints/i/2fa/register.ts    |  13 +-
 .../server/api/endpoints/i/2fa/remove-key.ts  |  17 +-
 .../server/api/endpoints/i/2fa/unregister.ts  |  13 +-
 .../src/server/api/endpoints/i/apps.ts        |  18 +-
 .../server/api/endpoints/i/authorized-apps.ts |  28 +-
 .../server/api/endpoints/i/change-password.ts |  18 +-
 .../server/api/endpoints/i/delete-account.ts  |  13 +-
 .../server/api/endpoints/i/export-blocking.ts |   8 +-
 .../api/endpoints/i/export-following.ts       |  20 +-
 .../src/server/api/endpoints/i/export-mute.ts |   8 +-
 .../server/api/endpoints/i/export-notes.ts    |   8 +-
 .../api/endpoints/i/export-user-lists.ts      |   8 +-
 .../src/server/api/endpoints/i/favorites.ts   |  31 +-
 .../server/api/endpoints/i/gallery/likes.ts   |  31 +-
 .../server/api/endpoints/i/gallery/posts.ts   |  31 +-
 .../endpoints/i/get-word-muted-notes-count.ts |  11 +-
 .../server/api/endpoints/i/import-blocking.ts |  18 +-
 .../api/endpoints/i/import-following.ts       |  18 +-
 .../server/api/endpoints/i/import-muting.ts   |  18 +-
 .../api/endpoints/i/import-user-lists.ts      |  18 +-
 .../server/api/endpoints/i/notifications.ts   |  63 +-
 .../src/server/api/endpoints/i/page-likes.ts  |  31 +-
 .../src/server/api/endpoints/i/pages.ts       |  31 +-
 .../backend/src/server/api/endpoints/i/pin.ts |  18 +-
 .../i/read-all-messaging-messages.ts          |   9 +-
 .../api/endpoints/i/read-all-unread-notes.ts  |   9 +-
 .../api/endpoints/i/read-announcement.ts      |  18 +-
 .../api/endpoints/i/regenerate-token.ts       |  13 +-
 .../api/endpoints/i/registry/get-all.ts       |  16 +-
 .../api/endpoints/i/registry/get-detail.ts    |  25 +-
 .../server/api/endpoints/i/registry/get.ts    |  25 +-
 .../endpoints/i/registry/keys-with-type.ts    |  16 +-
 .../server/api/endpoints/i/registry/keys.ts   |  16 +-
 .../server/api/endpoints/i/registry/remove.ts |  25 +-
 .../server/api/endpoints/i/registry/scopes.ts |   9 +-
 .../server/api/endpoints/i/registry/set.ts    |  26 +-
 .../server/api/endpoints/i/revoke-token.ts    |  14 +-
 .../server/api/endpoints/i/signin-history.ts  |  27 +-
 .../src/server/api/endpoints/i/unpin.ts       |  18 +-
 .../server/api/endpoints/i/update-email.ts    |  22 +-
 .../src/server/api/endpoints/i/update.ts      | 166 +--
 .../api/endpoints/i/user-group-invites.ts     |  31 +-
 .../server/api/endpoints/messaging/history.ts |  26 +-
 .../api/endpoints/messaging/messages.ts       |  49 +-
 .../endpoints/messaging/messages/create.ts    |  33 +-
 .../endpoints/messaging/messages/delete.ts    |  18 +-
 .../api/endpoints/messaging/messages/read.ts  |  18 +-
 .../backend/src/server/api/endpoints/meta.ts  |  18 +-
 .../server/api/endpoints/miauth/gen-token.ts  |  39 +-
 .../src/server/api/endpoints/mute/create.ts   |  18 +-
 .../src/server/api/endpoints/mute/delete.ts   |  18 +-
 .../src/server/api/endpoints/mute/list.ts     |  31 +-
 .../src/server/api/endpoints/my/apps.ts       |  26 +-
 .../backend/src/server/api/endpoints/notes.ts |  56 +-
 .../server/api/endpoints/notes/children.ts    |  36 +-
 .../src/server/api/endpoints/notes/clips.ts   |  18 +-
 .../api/endpoints/notes/conversation.ts       |  32 +-
 .../src/server/api/endpoints/notes/create.ts  | 123 +--
 .../src/server/api/endpoints/notes/delete.ts  |  18 +-
 .../api/endpoints/notes/favorites/create.ts   |  18 +-
 .../api/endpoints/notes/favorites/delete.ts   |  18 +-
 .../server/api/endpoints/notes/featured.ts    |  24 +-
 .../api/endpoints/notes/global-timeline.ts    |  46 +-
 .../api/endpoints/notes/hybrid-timeline.ts    |  64 +-
 .../api/endpoints/notes/local-timeline.ts     |  59 +-
 .../server/api/endpoints/notes/mentions.ts    |  42 +-
 .../endpoints/notes/polls/recommendation.ts   |  26 +-
 .../server/api/endpoints/notes/polls/vote.ts  |  23 +-
 .../server/api/endpoints/notes/reactions.ts   |  47 +-
 .../api/endpoints/notes/reactions/create.ts   |  23 +-
 .../api/endpoints/notes/reactions/delete.ts   |  18 +-
 .../src/server/api/endpoints/notes/renotes.ts |  36 +-
 .../src/server/api/endpoints/notes/replies.ts |  36 +-
 .../api/endpoints/notes/search-by-tag.ts      |  68 +-
 .../src/server/api/endpoints/notes/search.ts  |  57 +-
 .../src/server/api/endpoints/notes/show.ts    |  18 +-
 .../src/server/api/endpoints/notes/state.ts   |  18 +-
 .../endpoints/notes/thread-muting/create.ts   |  18 +-
 .../endpoints/notes/thread-muting/delete.ts   |  18 +-
 .../server/api/endpoints/notes/timeline.ts    |  64 +-
 .../server/api/endpoints/notes/translate.ts   |  22 +-
 .../server/api/endpoints/notes/unrenote.ts    |  18 +-
 .../api/endpoints/notes/user-list-timeline.ts |  69 +-
 .../api/endpoints/notes/watching/create.ts    |  18 +-
 .../api/endpoints/notes/watching/delete.ts    |  18 +-
 .../api/endpoints/notifications/create.ts     |  27 +-
 .../notifications/mark-all-as-read.ts         |   8 +-
 .../api/endpoints/notifications/read.ts       |  18 +-
 .../src/server/api/endpoints/page-push.ts     |  28 +-
 .../src/server/api/endpoints/pages/create.ts  |  70 +-
 .../src/server/api/endpoints/pages/delete.ts  |  18 +-
 .../server/api/endpoints/pages/featured.ts    |   8 +-
 .../src/server/api/endpoints/pages/like.ts    |  18 +-
 .../src/server/api/endpoints/pages/show.ts    |  28 +-
 .../src/server/api/endpoints/pages/unlike.ts  |  18 +-
 .../src/server/api/endpoints/pages/update.ts  |  72 +-
 .../backend/src/server/api/endpoints/ping.ts  |  11 +-
 .../src/server/api/endpoints/pinned-users.ts  |  11 +-
 .../src/server/api/endpoints/promo/read.ts    |  18 +-
 .../api/endpoints/request-reset-password.ts   |  22 +-
 .../src/server/api/endpoints/reset-db.ts      |  12 +-
 .../server/api/endpoints/reset-password.ts    |  22 +-
 .../src/server/api/endpoints/server-info.ts   |  12 +-
 .../backend/src/server/api/endpoints/stats.ts |  11 +-
 .../src/server/api/endpoints/sw/register.ts   |  27 +-
 .../src/server/api/endpoints/sw/unregister.ts |  13 +-
 .../backend/src/server/api/endpoints/test.ts  |  22 +
 .../api/endpoints/username/available.ts       |  17 +-
 .../backend/src/server/api/endpoints/users.ts |  60 +-
 .../src/server/api/endpoints/users/clips.ts   |  32 +-
 .../server/api/endpoints/users/followers.ts   |  46 +-
 .../server/api/endpoints/users/following.ts   |  46 +-
 .../api/endpoints/users/gallery/posts.ts      |  32 +-
 .../users/get-frequently-replied-users.ts     |  26 +-
 .../api/endpoints/users/groups/create.ts      |  17 +-
 .../api/endpoints/users/groups/delete.ts      |  18 +-
 .../users/groups/invitations/accept.ts        |  18 +-
 .../users/groups/invitations/reject.ts        |  18 +-
 .../api/endpoints/users/groups/invite.ts      |  23 +-
 .../api/endpoints/users/groups/joined.ts      |   8 +-
 .../api/endpoints/users/groups/leave.ts       |  18 +-
 .../api/endpoints/users/groups/owned.ts       |   8 +-
 .../server/api/endpoints/users/groups/pull.ts |  23 +-
 .../server/api/endpoints/users/groups/show.ts |  18 +-
 .../api/endpoints/users/groups/transfer.ts    |  23 +-
 .../api/endpoints/users/groups/update.ts      |  23 +-
 .../api/endpoints/users/lists/create.ts       |  17 +-
 .../api/endpoints/users/lists/delete.ts       |  18 +-
 .../server/api/endpoints/users/lists/list.ts  |   8 +-
 .../server/api/endpoints/users/lists/pull.ts  |  23 +-
 .../server/api/endpoints/users/lists/push.ts  |  23 +-
 .../server/api/endpoints/users/lists/show.ts  |  18 +-
 .../api/endpoints/users/lists/update.ts       |  23 +-
 .../src/server/api/endpoints/users/notes.ts   |  77 +-
 .../src/server/api/endpoints/users/pages.ts   |  32 +-
 .../server/api/endpoints/users/reactions.ts   |  46 +-
 .../api/endpoints/users/recommendation.ts     |  26 +-
 .../server/api/endpoints/users/relation.ts    |  26 +-
 .../api/endpoints/users/report-abuse.ts       |  23 +-
 .../users/search-by-username-and-host.ts      |  44 +-
 .../src/server/api/endpoints/users/search.ts  |  49 +-
 .../src/server/api/endpoints/users/show.ts    |  35 +-
 .../src/server/api/endpoints/users/stats.ts   |  18 +-
 .../src/server/api/openapi/gen-spec.ts        |  61 +-
 .../backend/src/server/api/openapi/schemas.ts |   4 +-
 .../backend/src/server/api/private/signup.ts  |   4 +-
 packages/backend/src/services/chart/core.ts   |  32 +
 packages/backend/test/api.ts                  | 975 +-----------------
 packages/backend/test/endpoints.ts            | 858 +++++++++++++++
 packages/backend/yarn.lock                    |  10 +
 320 files changed, 4395 insertions(+), 5939 deletions(-)
 create mode 100644 packages/backend/src/server/api/endpoints/test.ts
 create mode 100644 packages/backend/test/endpoints.ts

diff --git a/packages/backend/package.json b/packages/backend/package.json
index d143846f6..b9433f7f3 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -70,6 +70,7 @@
 		"@typescript-eslint/eslint-plugin": "5.12.0",
 		"@typescript-eslint/parser": "5.12.0",
 		"abort-controller": "3.0.0",
+		"ajv": "8.10.0",
 		"archiver": "5.3.0",
 		"autobind-decorator": "2.4.0",
 		"autwh": "0.1.0",
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 2dae954af..fbe5a1e42 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -65,16 +65,18 @@ export const refs = {
 
 // Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる
 // ObjType<r['properties']>を指定すると(なぜか)展開されずにPacked<'Hoge'>と表示される
-type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> =
-	r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
-	r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> :
-	r['properties'] extends Obj ? ObjType<r['properties']> :
+type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<Schema>; allOf?: ReadonlyArray<Schema> }> =
+	r['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
+	r['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<r['oneOf']> :
+	r['properties'] extends Obj ? ObjType<r['properties'], any> :
 	never;
 export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>;
 
-type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
+type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
 type StringDefToType<T extends TypeStringef> =
+	T extends 'null' ? null :
 	T extends 'boolean' ? boolean :
+	T extends 'integer' ? number :
 	T extends 'number' ? number :
 	T extends 'string' ? string | Date :
 	T extends 'array' ? ReadonlyArray<any> :
@@ -83,17 +85,18 @@ type StringDefToType<T extends TypeStringef> =
 
 // https://swagger.io/specification/?sbsearch=optional#schema-object
 type OfSchema = {
-	readonly anyOf?: ReadonlyArray<MinimumSchema>;
-	readonly oneOf?: ReadonlyArray<MinimumSchema>;
-	readonly allOf?: ReadonlyArray<MinimumSchema>;
+	readonly anyOf?: ReadonlyArray<Schema>;
+	readonly oneOf?: ReadonlyArray<Schema>;
+	readonly allOf?: ReadonlyArray<Schema>;
 }
 
-export interface MinimumSchema extends OfSchema {
+export interface Schema extends OfSchema {
 	readonly type?: TypeStringef;
 	readonly nullable?: boolean;
 	readonly optional?: boolean;
-	readonly items?: MinimumSchema;
+	readonly items?: Schema;
 	readonly properties?: Obj;
+	readonly required?: ReadonlyArray<keyof NonNullable<this['properties']>>;
 	readonly description?: string;
 	readonly example?: any;
 	readonly format?: string;
@@ -104,26 +107,33 @@ export interface MinimumSchema extends OfSchema {
 	readonly minLength?: number;
 }
 
-export interface Schema extends MinimumSchema {
-	readonly nullable: boolean;
-	readonly optional: boolean;
-}
-
-type NonUndefinedPropertyNames<T extends Obj> = {
-	[K in keyof T]: T[K]['optional'] extends true ? never : K
+type OptionalPropertyNames<T extends Obj> = {
+	[K in keyof T]: T[K]['optional'] extends true ? K : never
 }[keyof T];
 
-type UndefinedPropertyNames<T extends Obj> = {
-	[K in keyof T]: T[K]['optional'] extends true ? K : never
+type NonOptionalPropertyNames<T extends Obj> = {
+	[K in keyof T]: T[K]['optional'] extends false ? K : never
+}[keyof T];
+
+type DefaultPropertyNames<T extends Obj> = {
+	[K in keyof T]: T[K]['default'] extends null ? K :
+		T[K]['default'] extends string ? K :
+		T[K]['default'] extends number ? K :
+		T[K]['default'] extends boolean ? K :
+		T[K]['default'] extends Record<string, unknown> ? K :
+		never
 }[keyof T];
 
 export interface Obj { [key: string]: Schema; }
 
-export type ObjType<s extends Obj> =
-	{ -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } &
-	{ -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> };
+export type ObjType<s extends Obj, RequiredProps extends ReadonlyArray<keyof s>> =
+	{ -readonly [P in keyof s]?: SchemaType<s[P]> } &
+	{ -readonly [P in RequiredProps[number]]: SchemaType<s[P]> } &
+	{ -readonly [P in OptionalPropertyNames<s>]?: SchemaType<s[P]> } &
+	{ -readonly [P in NonOptionalPropertyNames<s>]: SchemaType<s[P]> } &
+	{ -readonly [P in DefaultPropertyNames<s>]: SchemaType<s[P]> };
 
-type NullOrUndefined<p extends MinimumSchema, T> =
+type NullOrUndefined<p extends Schema, T> =
 	p['nullable'] extends true
 		?	p['optional'] extends true
 			? (T | null | undefined)
@@ -137,10 +147,12 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
 
 // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
 // 単純にSchemaTypeDef<X>で判定するだけではダメ
-type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never;
+type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? SchemaType<X> : never;
 type ArrayUnion<T> = T extends any ? Array<T> : never; 
 
-export type SchemaTypeDef<p extends MinimumSchema> =
+export type SchemaTypeDef<p extends Schema> =
+	p['type'] extends 'null' ? null :
+	p['type'] extends 'integer' ? number :
 	p['type'] extends 'number' ? number :
 	p['type'] extends 'string' ? (
 		p['enum'] extends readonly string[] ?
@@ -151,22 +163,22 @@ export type SchemaTypeDef<p extends MinimumSchema> =
 	p['type'] extends 'boolean' ? boolean :
 	p['type'] extends 'object' ? (
 		p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
-		p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> :
-		p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
-		p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
+		p['properties'] extends NonNullable<Obj> ? ObjType<p['properties'], NonNullable<p['required']>> :
+		p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
+		p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
 		any
 	) :
 	p['type'] extends 'array' ? (
 		p['items'] extends OfSchema ? (
-			p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
-			p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
-			p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
+			p['items']['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
+			p['items']['oneOf'] extends ReadonlyArray<Schema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
+			p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
 			never
 		) :
-		p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] :
+		p['items'] extends NonNullable<Schema> ? SchemaTypeDef<p['items']>[] :
 		any[]
 	) :
-	p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> :
+	p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
 	any;
 
-export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>;
+export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts
index b2e6cee9b..cc7e4ca55 100644
--- a/packages/backend/src/models/repositories/drive-folder.ts
+++ b/packages/backend/src/models/repositories/drive-folder.ts
@@ -6,13 +6,6 @@ import { Packed } from '@/misc/schema';
 
 @EntityRepository(DriveFolder)
 export class DriveFolderRepository extends Repository<DriveFolder> {
-	public validateFolderName(name: string): boolean {
-		return (
-			(name.trim().length > 0) &&
-			(name.length <= 200)
-		);
-	}
-
 	public async pack(
 		src: DriveFolder['id'] | DriveFolder,
 		options?: {
diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts
index 0a342430b..d01d82c36 100644
--- a/packages/backend/src/models/repositories/messaging-message.ts
+++ b/packages/backend/src/models/repositories/messaging-message.ts
@@ -6,10 +6,6 @@ import { User } from '@/models/entities/user';
 
 @EntityRepository(MessagingMessage)
 export class MessagingMessageRepository extends Repository<MessagingMessage> {
-	public validateText(text: string): boolean {
-		return text.trim().length <= 1000 && text.trim() != '';
-	}
-
 	public async pack(
 		src: MessagingMessage['id'] | MessagingMessage,
 		me?: { id: User['id'] } | null | undefined,
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index 9a7fef497..a7e44d949 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -12,10 +12,6 @@ import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/popu
 
 @EntityRepository(Note)
 export class NoteRepository extends Repository<Note> {
-	public validateCw(x: string) {
-		return x.trim().length <= 100;
-	}
-
 	public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
 		// visibility が specified かつ自分が指定されていなかったら非表示
 		if (note.visibility === 'specified') {
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index 33b2b32fe..aea94b379 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -1,5 +1,5 @@
-import $ from 'cafy';
 import { EntityRepository, Repository, In, Not } from 'typeorm';
+import * as Ajv from 'ajv';
 import { User, ILocalUser, IRemoteUser } from '@/models/entities/user';
 import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index';
 import config from '@/config/index';
@@ -17,8 +17,26 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
 		Packed<'UserDetailed'> :
 	Packed<'UserLite'>;
 
+const ajv = new Ajv();
+
 @EntityRepository(User)
 export class UserRepository extends Repository<User> {
+	public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
+	public passwordSchema = { type: 'string', minLength: 1 } as const;
+	public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
+	public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
+	public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
+	public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
+
+	//#region Validators
+	public validateLocalUsername = ajv.compile(this.localUsernameSchema);
+	public validatePassword = ajv.compile(this.passwordSchema);
+	public validateName = ajv.compile(this.nameSchema);
+	public validateDescription = ajv.compile(this.descriptionSchema);
+	public validateLocation = ajv.compile(this.locationSchema);
+	public validateBirthday = ajv.compile(this.birthdaySchema);
+	//#endregion
+
 	public async getRelation(me: User['id'], target: User['id']) {
 		const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
 			Followings.findOne({
@@ -351,13 +369,4 @@ export class UserRepository extends Repository<User> {
 	public isRemoteUser(user: User | { host: User['host'] }): boolean {
 		return !this.isLocalUser(user);
 	}
-
-	//#region Validators
-	public validateLocalUsername = $.str.match(/^\w{1,20}$/);
-	public validatePassword = $.str.min(1);
-	public validateName = $.str.min(1).max(50);
-	public validateDescription = $.str.min(1).max(500);
-	public validateLocation = $.str.min(1).max(50);
-	public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
-	//#endregion
 }
diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts
index 362bbb0f5..cd7b9615b 100644
--- a/packages/backend/src/server/api/api-handler.ts
+++ b/packages/backend/src/server/api/api-handler.ts
@@ -5,7 +5,7 @@ import authenticate, { AuthenticationError } from './authenticate';
 import call from './call';
 import { ApiError } from './error';
 
-export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => {
+export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => {
 	const body = ctx.request.body;
 
 	const reply = (x?: any, y?: ApiError) => {
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 18143340a..ea457d955 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -2,7 +2,7 @@ import * as Koa from 'koa';
 import { performance } from 'perf_hooks';
 import { limiter } from './limiter';
 import { User } from '@/models/entities/user';
-import endpoints from './endpoints';
+import endpoints, { IEndpoint } from './endpoints';
 import { ApiError } from './error';
 import { apiLogger } from './logger';
 import { AccessToken } from '@/models/entities/access-token';
@@ -67,7 +67,7 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
 
 	if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) {
 		// Rate limit
-		await limiter(ep, user!).catch(e => {
+		await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => {
 			throw new ApiError({
 				message: 'Rate limit exceeded. Please try again later.',
 				code: 'RATE_LIMIT_EXCEEDED',
@@ -78,10 +78,10 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
 	}
 
 	// Cast non JSON input
-	if (ep.meta.requireFile && ep.meta.params) {
-		for (const k of Object.keys(ep.meta.params)) {
-			const param = ep.meta.params[k];
-			if (['Boolean', 'Number'].includes(param.validator.name) && typeof data[k] === 'string') {
+	if (ep.meta.requireFile) {
+		for (const k of Object.keys(ep.params)) {
+			const param = ep.params.properties![k];
+			if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
 				try {
 					data[k] = JSON.parse(data[k]);
 				} catch (e) {
@@ -91,8 +91,8 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
 						id: '0b5f1631-7c1a-41a6-b399-cce335f34d85',
 					}, {
 						param: k,
-						reason: `cannot cast to ${param.validator.name}`,
-					})
+						reason: `cannot cast to ${param.type}`,
+					});
 				}
 			}
 		}
diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts
index f8db7e374..786c94cf5 100644
--- a/packages/backend/src/server/api/common/signup.ts
+++ b/packages/backend/src/server/api/common/signup.ts
@@ -21,13 +21,13 @@ export async function signup(opts: {
 	let hash = passwordHash;
 
 	// Validate username
-	if (!Users.validateLocalUsername.ok(username)) {
+	if (!Users.validateLocalUsername(username)) {
 		throw new Error('INVALID_USERNAME');
 	}
 
 	if (password != null && passwordHash == null) {
 		// Validate password
-		if (!Users.validatePassword.ok(password)) {
+		if (!Users.validatePassword(password)) {
 			throw new Error('INVALID_PASSWORD');
 		}
 
diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts
index b6bb2da8a..061ade17b 100644
--- a/packages/backend/src/server/api/define.ts
+++ b/packages/backend/src/server/api/define.ts
@@ -1,12 +1,11 @@
 import * as fs from 'fs';
+import * as Ajv from 'ajv';
 import { ILocalUser } from '@/models/entities/user';
 import { IEndpointMeta } from './endpoints';
 import { ApiError } from './error';
-import { SchemaType } from '@/misc/schema';
+import { Schema, SchemaType } from '@/misc/schema';
 import { AccessToken } from '@/models/entities/access-token';
 
-type NonOptional<T> = T extends undefined ? never : T;
-
 type SimpleUserInfo = {
 	id: ILocalUser['id'];
 	createdAt: ILocalUser['createdAt'];
@@ -21,22 +20,24 @@ type SimpleUserInfo = {
 	showTimelineReplies: ILocalUser['showTimelineReplies'];
 };
 
-type Params<T extends IEndpointMeta> = {
-	[P in keyof T['params']]: NonNullable<T['params']>[P]['transform'] extends () => any
-		? ReturnType<NonNullable<T['params']>[P]['transform']>
-		: NonNullable<T['params']>[P]['default'] extends null | number | string
-			? NonOptional<ReturnType<NonNullable<T['params']>[P]['validator']['get']>[0]>
-			: ReturnType<NonNullable<T['params']>[P]['validator']['get']>[0];
-};
-
 export type Response = Record<string, any> | void;
 
-type executor<T extends IEndpointMeta> =
-	(params: Params<T>, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
+// TODO: paramsの型をT['params']のスキーマ定義から推論する
+type executor<T extends IEndpointMeta, Ps extends Schema> =
+	(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
 		Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
 
-export default function <T extends IEndpointMeta>(meta: T, cb: executor<T>)
+const ajv = new Ajv({
+	useDefaults: true,
+});
+
+ajv.addFormat('misskey:id', /^[a-z0-9]+$/);
+
+export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>)
 		: (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise<any> {
+
+	const validate = ajv.compile(paramDef);
+
 	return (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => {
 		function cleanup() {
 			fs.unlink(file.path, () => {});
@@ -48,42 +49,22 @@ export default function <T extends IEndpointMeta>(meta: T, cb: executor<T>)
 			id: '4267801e-70d1-416a-b011-4ee502885d8b',
 		}));
 
-		const [ps, pserr] = getParams(meta, params);
-		if (pserr) {
+		const valid = validate(params);
+		if (!valid) {
 			if (file) cleanup();
-			return Promise.reject(pserr);
-		}
 
-		return cb(ps, user, token, file, cleanup);
-	};
-}
-
-function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, ApiError | null] {
-	if (defs.params == null) return [params, null];
-
-	const x: any = {};
-	let err: ApiError | null = null;
-	Object.entries(defs.params).some(([k, def]) => {
-		const [v, e] = def.validator.get(params[k]);
-		if (e) {
-			err = new ApiError({
+			const errors = validate.errors!;
+			const err = new ApiError({
 				message: 'Invalid param.',
 				code: 'INVALID_PARAM',
 				id: '3d81ceae-475f-4600-b2a8-2bc116157532',
 			}, {
-				param: k,
-				reason: e.message,
+				param: errors[0].schemaPath,
+				reason: errors[0].message,
 			});
-			return true;
-		} else {
-			if (v === undefined && Object.prototype.hasOwnProperty.call(def, 'default')) {
-				x[k] = def.default;
-			} else {
-				x[k] = v;
-			}
-			if (def.transform) x[k] = def.transform(x[k]);
-			return false;
+			return Promise.reject(err);
 		}
-	});
-	return [x, err];
+
+		return cb(params, user, token, file, cleanup);
+	};
 }
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index bb4e972b8..32dbdeb31 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,6 +1,4 @@
-import { fileURLToPath } from 'url';
 import { dirname } from 'path';
-import { Context } from 'cafy';
 import * as path from 'path';
 import * as glob from 'glob';
 import { Schema } from '@/misc/schema';
@@ -9,23 +7,11 @@ import { Schema } from '@/misc/schema';
 const _filename = __filename;
 const _dirname = dirname(_filename);
 
-export type Param = {
-	validator: Context<any>;
-	transform?: any;
-	default?: any;
-	deprecated?: boolean;
-	ref?: string;
-};
-
 export interface IEndpointMeta {
 	readonly stability?: 'deprecated' | 'experimental' | 'stable';
 
 	readonly tags?: ReadonlyArray<string>;
 
-	readonly params?: {
-		readonly [key: string]: Param;
-	};
-
 	readonly errors?: {
 		readonly [key: string]: {
 			readonly message: string;
@@ -99,12 +85,15 @@ export interface IEndpointMeta {
 	 * パーミッションの実現に利用されます。
 	 */
 	readonly kind?: string;
+
+	readonly description?: string;
 }
 
 export interface IEndpoint {
 	name: string;
 	exec: any;
 	meta: IEndpointMeta;
+	params: Schema;
 }
 
 const files = glob.sync('**/*.js', {
@@ -118,6 +107,7 @@ const endpoints: IEndpoint[] = files.map(f => {
 		name: f.replace('.js', ''),
 		exec: ep.default,
 		meta: ep.meta || {},
+		params: ep.paramDef,
 	};
 });
 
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
index ed7b146d0..97b7bc7db 100644
--- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { AbuseUserReports } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -10,49 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		state: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		reporterOrigin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'combined',
-		},
-
-		targetUserOrigin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'combined',
-		},
-
-		forwarded: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -115,8 +70,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		state: { type: 'string', nullable: true, default: null },
+		reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" },
+		targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "combined" },
+		forwarded: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId);
 
 	switch (ps.state) {
@@ -134,7 +103,7 @@ export default define(meta, async (ps) => {
 		case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break;
 	}
 
-	const reports = await query.take(ps.limit!).getMany();
+	const reports = await query.take(ps.limit).getMany();
 
 	return await AbuseUserReports.packMany(reports);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index 20f123295..50b2eb042 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -5,16 +5,6 @@ import { signup } from '../../../common/signup';
 export const meta = {
 	tags: ['admin'],
 
-	params: {
-		username: {
-			validator: Users.validateLocalUsername,
-		},
-
-		password: {
-			validator: Users.validatePassword,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -28,8 +18,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		username: Users.localUsernameSchema,
+		password: Users.passwordSchema,
+	},
+	required: ['username', 'password'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, _me) => {
+export default define(meta, paramDef, async (ps, _me) => {
 	const me = _me ? await Users.findOneOrFail(_me.id) : null;
 	const noUsers = (await Users.count({
 		host: null,
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
index 1701c1e3a..99d61b2f3 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
@@ -1,26 +1,26 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Users } from '@/models/index';
 import { doPostSuspend } from '@/services/suspend-user';
 import { publishUserEvent } from '@/services/stream';
 import { createDeleteAccountJob } from '@/queue';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['admin'],
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
index 00ad2012f..9883efefe 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Ads } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -8,34 +7,24 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		url: {
-			validator: $.str.min(1),
-		},
-		memo: {
-			validator: $.str,
-		},
-		place: {
-			validator: $.str,
-		},
-		priority: {
-			validator: $.str,
-		},
-		ratio: {
-			validator: $.num.int().min(0),
-		},
-		expiresAt: {
-			validator: $.num.int(),
-		},
-		imageUrl: {
-			validator: $.str.min(1),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		url: { type: 'string', minLength: 1 },
+		memo: { type: 'string' },
+		place: { type: 'string' },
+		priority: { type: 'string' },
+		ratio: { type: 'integer' },
+		expiresAt: { type: 'integer' },
+		imageUrl: { type: 'string', minLength: 1 },
 	},
+	required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'imageUrl'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	await Ads.insert({
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
index c0124e248..dfd873f01 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Ads } from '@/models/index';
 import { ApiError } from '../../../error';
 
@@ -10,12 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchAd: {
 			message: 'No such ad.',
@@ -25,8 +17,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['id'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const ad = await Ads.findOne(ps.id);
 
 	if (ad == null) throw new ApiError(meta.errors.noSuchAd);
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts
index 7a83637f3..83fdedef7 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { Ads } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
@@ -9,29 +7,24 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId)
 		.andWhere('ad.expiresAt > :now', { now: new Date() });
 
-	const ads = await query.take(ps.limit!).getMany();
+	const ads = await query.take(ps.limit).getMany();
 
 	return ads;
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
index c2b09ab9c..bf4bce515 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Ads } from '@/models/index';
 import { ApiError } from '../../../error';
 
@@ -10,33 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-		memo: {
-			validator: $.str,
-		},
-		url: {
-			validator: $.str.min(1),
-		},
-		imageUrl: {
-			validator: $.str.min(1),
-		},
-		place: {
-			validator: $.str,
-		},
-		priority: {
-			validator: $.str,
-		},
-		ratio: {
-			validator: $.num.int().min(0),
-		},
-		expiresAt: {
-			validator: $.num.int(),
-		},
-	},
-
 	errors: {
 		noSuchAd: {
 			message: 'No such ad.',
@@ -46,8 +17,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+		memo: { type: 'string' },
+		url: { type: 'string', minLength: 1 },
+		imageUrl: { type: 'string', minLength: 1 },
+		place: { type: 'string' },
+		priority: { type: 'string' },
+		ratio: { type: 'integer' },
+		expiresAt: { type: 'integer' },
+	},
+	required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const ad = await Ads.findOne(ps.id);
 
 	if (ad == null) throw new ApiError(meta.errors.noSuchAd);
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
index 24c4caa37..8d2bda978 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Announcements } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -9,18 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		title: {
-			validator: $.str.min(1),
-		},
-		text: {
-			validator: $.str.min(1),
-		},
-		imageUrl: {
-			validator: $.nullable.str.min(1),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -57,8 +44,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		title: { type: 'string', minLength: 1 },
+		text: { type: 'string', minLength: 1 },
+		imageUrl: { type: 'string', nullable: true, minLength: 1 },
+	},
+	required: ['title', 'text', 'imageUrl'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const announcement = await Announcements.insert({
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
index 5548f9900..f0141ca59 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Announcements } from '@/models/index';
 import { ApiError } from '../../../error';
 
@@ -10,12 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchAnnouncement: {
 			message: 'No such announcement.',
@@ -25,8 +17,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['id'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const announcement = await Announcements.findOne(ps.id);
 
 	if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index e5cc53ccd..993f9877d 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { Announcements, AnnouncementReads } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
@@ -10,21 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -69,11 +52,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
 
-	const announcements = await query.take(ps.limit!).getMany();
+	const announcements = await query.take(ps.limit).getMany();
 
 	for (const announcement of announcements) {
 		(announcement as any).reads = await AnnouncementReads.count({
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
index f66293bb1..f905616b3 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Announcements } from '@/models/index';
 import { ApiError } from '../../../error';
 
@@ -10,21 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-		title: {
-			validator: $.str.min(1),
-		},
-		text: {
-			validator: $.str.min(1),
-		},
-		imageUrl: {
-			validator: $.nullable.str.min(1),
-		},
-	},
-
 	errors: {
 		noSuchAnnouncement: {
 			message: 'No such announcement.',
@@ -34,8 +17,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+		title: { type: 'string', minLength: 1 },
+		text: { type: 'string', minLength: 1 },
+		imageUrl: { type: 'string', nullable: true, minLength: 1 },
+	},
+	required: ['id', 'title', 'text', 'imageUrl'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const announcement = await Announcements.findOne(ps.id);
 
 	if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
index 249e63a0f..41e2c2348 100644
--- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
@@ -1,24 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
 import { deleteFile } from '@/services/drive/delete-file';
 import { DriveFiles } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['admin'],
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const files = await DriveFiles.find({
 		userId: ps.userId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
index acabbfef5..9b127eb5d 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
@@ -8,7 +8,13 @@ export const meta = {
 	requireModerator: true,
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	createCleanRemoteFilesJob();
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
index 452e7069a..1c63af6a1 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
@@ -10,8 +10,14 @@ export const meta = {
 	requireModerator: true,
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const files = await DriveFiles.find({
 		userId: IsNull(),
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
index 264f54986..56a8c1c64 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { DriveFiles } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['admin'],
@@ -10,39 +8,6 @@ export const meta = {
 	requireCredential: false,
 	requireModerator: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		type: {
-			validator: $.optional.nullable.str.match(/^[a-zA-Z0-9\/\-*]+$/),
-		},
-
-		origin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'local',
-		},
-
-		hostname: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -54,8 +19,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
+		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
+		hostname: { type: 'string', nullable: true, default: null },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId);
 
 	if (ps.origin === 'local') {
@@ -76,7 +54,7 @@ export default define(meta, async (ps, me) => {
 		}
 	}
 
-	const files = await query.take(ps.limit!).getMany();
+	const files = await query.take(ps.limit).getMany();
 
 	return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index 5d9a1f270..bb1ed1072 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { DriveFiles } from '@/models/index';
@@ -10,16 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		fileId: {
-			validator: $.optional.type(ID),
-		},
-
-		url: {
-			validator: $.optional.str,
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -161,8 +149,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+		url: { type: 'string' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const file = ps.fileId ? await DriveFiles.findOne(ps.fileId) : await DriveFiles.findOne({
 		where: [{
 			url: ps.url,
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index f0fd73c27..9aeb04d24 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection, In } from 'typeorm';
 import { ApiError } from '../../../error';
@@ -10,20 +8,23 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		ids: {
-			validator: $.arr($.type(ID)),
-		},
-
-		aliases: {
-			validator: $.arr($.str),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		aliases: { type: 'array', items: {
+			type: 'string',
+		} },
 	},
+	required: ['ids', 'aliases'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const emojis = await Emojis.find({
 		id: In(ps.ids),
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 1dfeae262..718ecae4b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -1,11 +1,9 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Emojis, DriveFiles } from '@/models/index';
 import { genId } from '@/misc/gen-id';
 import { getConnection } from 'typeorm';
 import { insertModerationLog } from '@/services/insert-moderation-log';
 import { ApiError } from '../../../error';
-import { ID } from '@/misc/cafy-id';
 import rndstr from 'rndstr';
 import { publishBroadcastStream } from '@/services/stream';
 
@@ -15,12 +13,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -30,8 +22,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index 17cbf208a..fc20ff0a8 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -1,11 +1,9 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Emojis } from '@/models/index';
 import { genId } from '@/misc/gen-id';
 import { getConnection } from 'typeorm';
 import { ApiError } from '../../../error';
 import { DriveFile } from '@/models/entities/drive-file';
-import { ID } from '@/misc/cafy-id';
 import { uploadFromUrl } from '@/services/drive/upload-from-url';
 import { publishBroadcastStream } from '@/services/stream';
 
@@ -15,12 +13,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		emojiId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchEmoji: {
 			message: 'No such emoji.',
@@ -42,8 +34,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		emojiId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['emojiId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const emoji = await Emojis.findOne(ps.emojiId);
 
 	if (emoji == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index 797a5de67..792e72ff9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection, In } from 'typeorm';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -11,16 +9,20 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		ids: {
-			validator: $.arr($.type(ID)),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
 	},
+	required: ['ids'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const emojis = await Emojis.find({
 		id: In(ps.ids),
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index 158043902..15d44b3a0 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection } from 'typeorm';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -12,12 +10,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchEmoji: {
 			message: 'No such emoji.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['id'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const emoji = await Emojis.findOne(ps.id);
 
 	if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index 8856a38f2..5ee6dd4c8 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -1,21 +1,22 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { createImportCustomEmojisJob } from '@/queue/index';
 import ms from 'ms';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	secure: true,
 	requireCredential: true,
 	requireModerator: true,
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
+} as const;
+
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['fileId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createImportCustomEmojisJob(user, ps.fileId);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index 6e502547f..865715968 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -1,9 +1,7 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Emojis } from '@/models/index';
 import { toPuny } from '@/misc/convert-host';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['admin'],
@@ -11,31 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		query: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -77,8 +50,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		query: { type: 'string', nullable: true, default: null },
+		host: { type: 'string', nullable: true, default: null },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
 
 	if (ps.host == null) {
@@ -93,7 +78,7 @@ export default define(meta, async (ps) => {
 
 	const emojis = await q
 		.orderBy('emoji.id', 'DESC')
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return Emojis.packMany(emojis);
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index 76ef190f9..0e23132d9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Emojis } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
-import { ID } from '@/misc/cafy-id';
 import { Emoji } from '@/models/entities/emoji';
 
 export const meta = {
@@ -11,26 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		query: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -72,8 +50,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		query: { type: 'string', nullable: true, default: null },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId)
 		.andWhere(`emoji.host IS NULL`);
 
@@ -81,7 +70,7 @@ export default define(meta, async (ps) => {
 
 	if (ps.query) {
 		//q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` });
-		//const emojis = await q.take(ps.limit!).getMany();
+		//const emojis = await q.take(ps.limit).getMany();
 
 		emojis = await q.getMany();
 
@@ -90,9 +79,9 @@ export default define(meta, async (ps) => {
 			emoji.aliases.some(a => a.includes(ps.query!)) ||
 			emoji.category?.includes(ps.query!));
 
-		emojis.splice(ps.limit! + 1);
+		emojis.splice(ps.limit + 1);
 	} else {
-		emojis = await q.take(ps.limit!).getMany();
+		emojis = await q.take(ps.limit).getMany();
 	}
 
 	return Emojis.packMany(emojis);
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index c49f84b7f..e6ccbf684 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection, In } from 'typeorm';
 import { ApiError } from '../../../error';
@@ -10,20 +8,23 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		ids: {
-			validator: $.arr($.type(ID)),
-		},
-
-		aliases: {
-			validator: $.arr($.str),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		aliases: { type: 'array', items: {
+			type: 'string',
+		} },
 	},
+	required: ['ids', 'aliases'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const emojis = await Emojis.find({
 		id: In(ps.ids),
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 06197820f..30a1c28af 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection, In } from 'typeorm';
 import { ApiError } from '../../../error';
@@ -10,20 +8,23 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		ids: {
-			validator: $.arr($.type(ID)),
-		},
-
-		aliases: {
-			validator: $.arr($.str),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		aliases: { type: 'array', items: {
+			type: 'string',
+		} },
 	},
+	required: ['ids', 'aliases'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	await Emojis.update({
 		id: In(ps.ids),
 	}, {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index f0645f111..9679e4a36 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection, In } from 'typeorm';
 import { ApiError } from '../../../error';
@@ -10,20 +8,21 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		ids: {
-			validator: $.arr($.type(ID)),
-		},
-
-		category: {
-			validator: $.optional.nullable.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		category: { type: 'string', nullable: true },
 	},
+	required: ['ids'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	await Emojis.update({
 		id: In(ps.ids),
 	}, {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 54a2cf951..da5a5f005 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
 import { Emojis } from '@/models/index';
 import { getConnection } from 'typeorm';
 import { ApiError } from '../../../error';
@@ -11,24 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		id: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.str,
-		},
-
-		category: {
-			validator: $.optional.nullable.str,
-		},
-
-		aliases: {
-			validator: $.arr($.str),
-		},
-	},
-
 	errors: {
 		noSuchEmoji: {
 			message: 'No such emoji.',
@@ -38,8 +18,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		id: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string' },
+		category: { type: 'string', nullable: true },
+		aliases: { type: 'array', items: {
+			type: 'string',
+		} },
+	},
+	required: ['id', 'name', 'aliases'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const emoji = await Emojis.findOne(ps.id);
 
 	if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
index db023c6f0..e98de40ff 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { deleteFile } from '@/services/drive/delete-file';
 import { DriveFiles } from '@/models/index';
@@ -8,16 +7,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		host: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
 	},
+	required: ['host'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const files = await DriveFiles.find({
 		userHost: ps.host,
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
index b68252ef2..d6e3a15c6 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Instances } from '@/models/index';
 import { toPuny } from '@/misc/convert-host';
@@ -9,16 +8,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		host: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
 	},
+	required: ['host'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const instance = await Instances.findOne({ host: toPuny(ps.host) });
 
 	if (instance == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 4de8ad133..7c2d6dc39 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import deleteFollowing from '@/services/following/delete';
 import { Followings, Users } from '@/models/index';
@@ -8,16 +7,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		host: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
 	},
+	required: ['host'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const followings = await Followings.find({
 		followerHost: ps.host,
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index 6ac2f1f46..04cad3b9f 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Instances } from '@/models/index';
 import { toPuny } from '@/misc/convert-host';
@@ -8,20 +7,19 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		host: {
-			validator: $.str,
-		},
-
-		isSuspended: {
-			validator: $.bool,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
+		isSuspended: { type: 'boolean' },
 	},
+	required: ['host', 'isSuspended'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const instance = await Instances.findOne({ host: toPuny(ps.host) });
 
 	if (instance == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
index 9a2bccec7..9379379ae 100644
--- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
@@ -6,13 +6,16 @@ export const meta = {
 	requireModerator: true,
 
 	tags: ['admin'],
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const stats = await
 		getConnection().query(`SELECT * FROM pg_indexes;`)
 		.then(recs => {
diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
index 1c5f25067..28856cd41 100644
--- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
@@ -7,9 +7,6 @@ export const meta = {
 
 	tags: ['admin'],
 
-	params: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -22,8 +19,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const sizes = await
 		getConnection().query(`
 			SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts
index 3428709c0..a21777937 100644
--- a/packages/backend/src/server/api/endpoints/admin/invite.ts
+++ b/packages/backend/src/server/api/endpoints/admin/invite.ts
@@ -9,8 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -26,8 +24,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const code = rndstr({
 		length: 8,
 		chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns)
diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
index 0308cf276..6b7e549c3 100644
--- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { Users } from '@/models/index';
 
@@ -8,16 +6,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireAdmin: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
index bdb976e9e..585d6fbfa 100644
--- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { Users } from '@/models/index';
 
@@ -8,16 +6,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireAdmin: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index f2735ac9f..2ba35306d 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getNote } from '../../../common/getters';
@@ -11,16 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		expiresAt: {
-			validator: $.num.int(),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -36,8 +24,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		expiresAt: { type: 'integer' },
+	},
+	required: ['noteId', 'expiresAt'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
index 3c8e7a27a..27bb9cc85 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
@@ -7,12 +7,16 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	destroy();
 
 	insertModerationLog(me, 'clearQueue');
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index 4760e2c31..38eac5a13 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -8,9 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -35,8 +32,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const jobs = await deliverQueue.getJobs(['delayed']);
 
 	const res = [] as [string, number][];
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index a95aabc50..9669cfd3b 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -8,9 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -35,8 +32,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const jobs = await inboxQueue.getJobs(['delayed']);
 
 	const res = [] as [string, number][];
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
index dab0be5db..3f7e6be5f 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
@@ -7,8 +7,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -33,8 +31,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const deliverJobCounts = await deliverQueue.getJobCounts();
 	const inboxJobCounts = await inboxQueue.getJobCounts();
 	const dbJobCounts = await dbQueue.getJobCounts();
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
index 65890a00f..ecf77b87d 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
@@ -1,5 +1,4 @@
 import { URL } from 'url';
-import $ from 'cafy';
 import define from '../../../define';
 import { addRelay } from '@/services/relay';
 import { ApiError } from '../../../error';
@@ -10,12 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		inbox: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 		invalidUrl: {
 			message: 'Invalid URL',
@@ -52,8 +45,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		inbox: { type: 'string' },
+	},
+	required: ['inbox'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	try {
 		if (new URL(ps.inbox).protocol !== 'https:') throw 'https only';
 	} catch {
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts
index bdddf1337..6cdaa418a 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts
@@ -7,9 +7,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -42,7 +39,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	return await listRelay();
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
index 4b04e620c..d452c2e6a 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { removeRelay } from '@/services/relay';
 
@@ -7,15 +6,17 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		inbox: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		inbox: { type: 'string' },
 	},
+	required: ['inbox'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	return await removeRelay(ps.inbox);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index b6cf1ee2d..87424782c 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import * as bcrypt from 'bcryptjs';
 import rndstr from 'rndstr';
@@ -11,12 +9,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -31,8 +23,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index a47b69ec1..4139f7761 100644
--- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
+++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { AbuseUserReports, Users } from '@/models/index';
 import { getInstanceActor } from '@/services/instance-actor';
@@ -12,22 +10,19 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		reportId: {
-			validator: $.type(ID),
-		},
-
-		forward: {
-			validator: $.optional.boolean,
-			required: false,
-			default: false,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		reportId: { type: 'string', format: 'misskey:id' },
+		forward: { type: 'boolean', default: false },
 	},
+	required: ['reportId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const report = await AbuseUserReports.findOne(ps.reportId);
 
 	if (report == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts
index c2972c35f..2ebe7e0bc 100644
--- a/packages/backend/src/server/api/endpoints/admin/send-email.ts
+++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { sendEmail } from '@/services/send-email';
 
@@ -7,21 +6,19 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		to: {
-			validator: $.str,
-		},
-		subject: {
-			validator: $.str,
-		},
-		text: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		to: { type: 'string' },
+		subject: { type: 'string' },
+		text: { type: 'string' },
 	},
+	required: ['to', 'subject', 'text'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	await sendEmail(ps.to, ps.subject, ps.text, ps.text);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts
index cd282e364..44e069d60 100644
--- a/packages/backend/src/server/api/endpoints/admin/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts
@@ -10,9 +10,6 @@ export const meta = {
 
 	tags: ['admin', 'meta'],
 
-	params: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -90,8 +87,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const memStats = await si.mem();
 	const fsStats = await si.fsSize();
 	const netInterface = await si.networkInterfaceDefault();
diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
index 84e2b84bb..c08acaeca 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ModerationLogs } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -10,21 +8,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -65,11 +48,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const query = makePaginationQuery(ModerationLogs.createQueryBuilder('report'), ps.sinceId, ps.untilId);
 
-	const reports = await query.take(ps.limit!).getMany();
+	const reports = await query.take(ps.limit).getMany();
 
 	return await ModerationLogs.packMany(reports);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index 0d0cc9087..b883aff49 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Users } from '@/models/index';
 
@@ -9,20 +7,22 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		nullable: false, optional: false,
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts
index d3dde99b7..c373021d0 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Users } from '@/models/index';
 
@@ -8,61 +7,6 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-
-		sort: {
-			validator: $.optional.str.or([
-				'+follower',
-				'-follower',
-				'+createdAt',
-				'-createdAt',
-				'+updatedAt',
-				'-updatedAt',
-			]),
-		},
-
-		state: {
-			validator: $.optional.str.or([
-				'all',
-				'available',
-				'admin',
-				'moderator',
-				'adminOrModerator',
-				'silenced',
-				'suspended',
-			]),
-			default: 'all',
-		},
-
-		origin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'local',
-		},
-
-		username: {
-			validator: $.optional.str,
-			default: null,
-		},
-
-		hostname: {
-			validator: $.optional.str,
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		nullable: false, optional: false,
@@ -74,8 +18,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+		sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
+		state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" },
+		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
+		username: { type: 'string', default: null },
+		hostname: { type: 'string', default: null },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Users.createQueryBuilder('user');
 
 	switch (ps.state) {
@@ -111,7 +69,7 @@ export default define(meta, async (ps, me) => {
 		default: query.orderBy('user.id', 'ASC'); break;
 	}
 
-	query.take(ps.limit!);
+	query.take(ps.limit);
 	query.skip(ps.offset);
 
 	const users = await query.getMany();
diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
index 872bd2a6a..df547184a 100644
--- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Users } from '@/models/index';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -9,16 +7,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 2bb1875fc..31867a405 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import deleteFollowing from '@/services/following/delete';
 import { Users, Followings, Notifications } from '@/models/index';
@@ -13,16 +11,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
index a4c6ff2ad..42c7e776a 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Users } from '@/models/index';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -9,16 +7,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
index 5ab56d51c..011f48527 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Users } from '@/models/index';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -10,16 +8,18 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId as string);
 
 	if (user == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 1764d230a..b845eb4d5 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -1,310 +1,107 @@
-import $ from 'cafy';
 import define from '../../define';
 import { getConnection } from 'typeorm';
 import { Meta } from '@/models/entities/meta';
 import { insertModerationLog } from '@/services/insert-moderation-log';
 import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['admin'],
 
 	requireCredential: true,
 	requireAdmin: true,
-
-	params: {
-		disableRegistration: {
-			validator: $.optional.nullable.bool,
-		},
-
-		disableLocalTimeline: {
-			validator: $.optional.nullable.bool,
-		},
-
-		disableGlobalTimeline: {
-			validator: $.optional.nullable.bool,
-		},
-
-		useStarForReactionFallback: {
-			validator: $.optional.nullable.bool,
-		},
-
-		pinnedUsers: {
-			validator: $.optional.nullable.arr($.str),
-		},
-
-		hiddenTags: {
-			validator: $.optional.nullable.arr($.str),
-		},
-
-		blockedHosts: {
-			validator: $.optional.nullable.arr($.str),
-		},
-
-		themeColor: {
-			validator: $.optional.nullable.str,
-		},
-
-		mascotImageUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		bannerUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		errorImageUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		iconUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		backgroundImageUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		logoImageUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		name: {
-			validator: $.optional.nullable.str,
-		},
-
-		description: {
-			validator: $.optional.nullable.str,
-		},
-
-		maxNoteTextLength: {
-			validator: $.optional.num.min(0).max(DB_MAX_NOTE_TEXT_LENGTH),
-		},
-
-		localDriveCapacityMb: {
-			validator: $.optional.num.min(0),
-		},
-
-		remoteDriveCapacityMb: {
-			validator: $.optional.num.min(0),
-		},
-
-		cacheRemoteFiles: {
-			validator: $.optional.bool,
-		},
-
-		proxyRemoteFiles: {
-			validator: $.optional.bool,
-		},
-
-		emailRequiredForSignup: {
-			validator: $.optional.bool,
-		},
-
-		enableHcaptcha: {
-			validator: $.optional.bool,
-		},
-
-		hcaptchaSiteKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		hcaptchaSecretKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		enableRecaptcha: {
-			validator: $.optional.bool,
-		},
-
-		recaptchaSiteKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		recaptchaSecretKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		proxyAccountId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		maintainerName: {
-			validator: $.optional.nullable.str,
-		},
-
-		maintainerEmail: {
-			validator: $.optional.nullable.str,
-		},
-
-		pinnedPages: {
-			validator: $.optional.arr($.str),
-		},
-
-		pinnedClipId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		langs: {
-			validator: $.optional.arr($.str),
-		},
-
-		summalyProxy: {
-			validator: $.optional.nullable.str,
-		},
-
-		deeplAuthKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		deeplIsPro: {
-			validator: $.optional.bool,
-		},
-
-		enableTwitterIntegration: {
-			validator: $.optional.bool,
-		},
-
-		twitterConsumerKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		twitterConsumerSecret: {
-			validator: $.optional.nullable.str,
-		},
-
-		enableGithubIntegration: {
-			validator: $.optional.bool,
-		},
-
-		githubClientId: {
-			validator: $.optional.nullable.str,
-		},
-
-		githubClientSecret: {
-			validator: $.optional.nullable.str,
-		},
-
-		enableDiscordIntegration: {
-			validator: $.optional.bool,
-		},
-
-		discordClientId: {
-			validator: $.optional.nullable.str,
-		},
-
-		discordClientSecret: {
-			validator: $.optional.nullable.str,
-		},
-
-		enableEmail: {
-			validator: $.optional.bool,
-		},
-
-		email: {
-			validator: $.optional.nullable.str,
-		},
-
-		smtpSecure: {
-			validator: $.optional.bool,
-		},
-
-		smtpHost: {
-			validator: $.optional.nullable.str,
-		},
-
-		smtpPort: {
-			validator: $.optional.nullable.num,
-		},
-
-		smtpUser: {
-			validator: $.optional.nullable.str,
-		},
-
-		smtpPass: {
-			validator: $.optional.nullable.str,
-		},
-
-		enableServiceWorker: {
-			validator: $.optional.bool,
-		},
-
-		swPublicKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		swPrivateKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		tosUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		repositoryUrl: {
-			validator: $.optional.str,
-		},
-
-		feedbackUrl: {
-			validator: $.optional.str,
-		},
-
-		useObjectStorage: {
-			validator: $.optional.bool,
-		},
-
-		objectStorageBaseUrl: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStorageBucket: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStoragePrefix: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStorageEndpoint: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStorageRegion: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStoragePort: {
-			validator: $.optional.nullable.num,
-		},
-
-		objectStorageAccessKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStorageSecretKey: {
-			validator: $.optional.nullable.str,
-		},
-
-		objectStorageUseSSL: {
-			validator: $.optional.bool,
-		},
-
-		objectStorageUseProxy: {
-			validator: $.optional.bool,
-		},
-
-		objectStorageSetPublicRead: {
-			validator: $.optional.bool,
-		},
-
-		objectStorageS3ForcePathStyle: {
-			validator: $.optional.bool,
-		},
+} as const;
+
+const paramDef = {
+	type: 'object',
+	properties: {
+		disableRegistration: { type: 'boolean', nullable: true },
+		disableLocalTimeline: { type: 'boolean', nullable: true },
+		disableGlobalTimeline: { type: 'boolean', nullable: true },
+		useStarForReactionFallback: { type: 'boolean', nullable: true },
+		pinnedUsers: { type: 'array', nullable: true, items: {
+			type: 'string',
+		} },
+		hiddenTags: { type: 'array', nullable: true, items: {
+			type: 'string',
+		} },
+		blockedHosts: { type: 'array', nullable: true, items: {
+			type: 'string',
+		} },
+		themeColor: { type: 'string', nullable: true },
+		mascotImageUrl: { type: 'string', nullable: true },
+		bannerUrl: { type: 'string', nullable: true },
+		errorImageUrl: { type: 'string', nullable: true },
+		iconUrl: { type: 'string', nullable: true },
+		backgroundImageUrl: { type: 'string', nullable: true },
+		logoImageUrl: { type: 'string', nullable: true },
+		name: { type: 'string', nullable: true },
+		description: { type: 'string', nullable: true },
+		maxNoteTextLength: { type: 'integer', maximum: 8192 },
+		localDriveCapacityMb: { type: 'integer' },
+		remoteDriveCapacityMb: { type: 'integer' },
+		cacheRemoteFiles: { type: 'boolean' },
+		proxyRemoteFiles: { type: 'boolean' },
+		emailRequiredForSignup: { type: 'boolean' },
+		enableHcaptcha: { type: 'boolean' },
+		hcaptchaSiteKey: { type: 'string', nullable: true },
+		hcaptchaSecretKey: { type: 'string', nullable: true },
+		enableRecaptcha: { type: 'boolean' },
+		recaptchaSiteKey: { type: 'string', nullable: true },
+		recaptchaSecretKey: { type: 'string', nullable: true },
+		proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
+		maintainerName: { type: 'string', nullable: true },
+		maintainerEmail: { type: 'string', nullable: true },
+		pinnedPages: { type: 'array', items: {
+			type: 'string',
+		} },
+		pinnedClipId: { type: 'string', format: 'misskey:id', nullable: true },
+		langs: { type: 'array', items: {
+			type: 'string',
+		} },
+		summalyProxy: { type: 'string', nullable: true },
+		deeplAuthKey: { type: 'string', nullable: true },
+		deeplIsPro: { type: 'boolean' },
+		enableTwitterIntegration: { type: 'boolean' },
+		twitterConsumerKey: { type: 'string', nullable: true },
+		twitterConsumerSecret: { type: 'string', nullable: true },
+		enableGithubIntegration: { type: 'boolean' },
+		githubClientId: { type: 'string', nullable: true },
+		githubClientSecret: { type: 'string', nullable: true },
+		enableDiscordIntegration: { type: 'boolean' },
+		discordClientId: { type: 'string', nullable: true },
+		discordClientSecret: { type: 'string', nullable: true },
+		enableEmail: { type: 'boolean' },
+		email: { type: 'string', nullable: true },
+		smtpSecure: { type: 'boolean' },
+		smtpHost: { type: 'string', nullable: true },
+		smtpPort: { type: 'integer', nullable: true },
+		smtpUser: { type: 'string', nullable: true },
+		smtpPass: { type: 'string', nullable: true },
+		enableServiceWorker: { type: 'boolean' },
+		swPublicKey: { type: 'string', nullable: true },
+		swPrivateKey: { type: 'string', nullable: true },
+		tosUrl: { type: 'string', nullable: true },
+		repositoryUrl: { type: 'string' },
+		feedbackUrl: { type: 'string' },
+		useObjectStorage: { type: 'boolean' },
+		objectStorageBaseUrl: { type: 'string', nullable: true },
+		objectStorageBucket: { type: 'string', nullable: true },
+		objectStoragePrefix: { type: 'string', nullable: true },
+		objectStorageEndpoint: { type: 'string', nullable: true },
+		objectStorageRegion: { type: 'string', nullable: true },
+		objectStoragePort: { type: 'integer', nullable: true },
+		objectStorageAccessKey: { type: 'string', nullable: true },
+		objectStorageSecretKey: { type: 'string', nullable: true },
+		objectStorageUseSSL: { type: 'boolean' },
+		objectStorageUseProxy: { type: 'boolean' },
+		objectStorageSetPublicRead: { type: 'boolean' },
+		objectStorageS3ForcePathStyle: { type: 'boolean' },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const set = {} as Partial<Meta>;
 
 	if (typeof ps.disableRegistration === 'boolean') {
diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts
index 4229ef0d2..07a4517bd 100644
--- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts
+++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { getConnection } from 'typeorm';
 import { insertModerationLog } from '@/services/insert-moderation-log';
@@ -8,19 +7,19 @@ export const meta = {
 
 	requireCredential: true,
 	requireModerator: true,
+} as const;
 
-	params: {
-		full: {
-			validator: $.bool,
-		},
-		analyze: {
-			validator: $.bool,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		full: { type: 'boolean' },
+		analyze: { type: 'boolean' },
 	},
+	required: ['full', 'analyze'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const params: string[] = [];
 
 	if (ps.full) {
diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts
index 22c13743a..bea3a287f 100644
--- a/packages/backend/src/server/api/endpoints/announcements.ts
+++ b/packages/backend/src/server/api/endpoints/announcements.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../define';
 import { Announcements, AnnouncementReads } from '@/models/index';
 import { makePaginationQuery } from '../common/make-pagination-query';
@@ -9,26 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		withUnreads: {
-			validator: $.optional.boolean,
-			default: false,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -73,11 +51,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		withUnreads: { type: 'boolean', default: false },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
 
-	const announcements = await query.take(ps.limit!).getMany();
+	const announcements = await query.take(ps.limit).getMany();
 
 	if (user) {
 		const reads = (await AnnouncementReads.find({
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index 2092d177b..16ba065b9 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../define';
 import { genId } from '@/misc/gen-id';
 import { Antennas, UserLists, UserGroupJoinings } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 import { ApiError } from '../../error';
 import { publishInternalEvent } from '@/services/stream';
 
@@ -13,52 +11,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		name: {
-			validator: $.str.range(1, 100),
-		},
-
-		src: {
-			validator: $.str.or(['home', 'all', 'users', 'list', 'group']),
-		},
-
-		userListId: {
-			validator: $.nullable.optional.type(ID),
-		},
-
-		userGroupId: {
-			validator: $.nullable.optional.type(ID),
-		},
-
-		keywords: {
-			validator: $.arr($.arr($.str)),
-		},
-
-		excludeKeywords: {
-			validator: $.arr($.arr($.str)),
-		},
-
-		users: {
-			validator: $.arr($.str),
-		},
-
-		caseSensitive: {
-			validator: $.bool,
-		},
-
-		withReplies: {
-			validator: $.bool,
-		},
-
-		withFile: {
-			validator: $.bool,
-		},
-
-		notify: {
-			validator: $.bool,
-		},
-	},
-
 	errors: {
 		noSuchUserList: {
 			message: 'No such user list.',
@@ -80,8 +32,36 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
+		userListId: { type: 'string', format: 'misskey:id', nullable: true },
+		userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
+		keywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		excludeKeywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		users: { type: 'array', items: {
+			type: 'string',
+		} },
+		caseSensitive: { type: 'boolean' },
+		withReplies: { type: 'boolean' },
+		withFile: { type: 'boolean' },
+		notify: { type: 'boolean' },
+	},
+	required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let userList;
 	let userGroupJoining;
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts
index b2793fc70..c14008477 100644
--- a/packages/backend/src/server/api/endpoints/antennas/delete.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Antennas } from '@/models/index';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		antennaId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchAntenna: {
 			message: 'No such antenna.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		antennaId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['antennaId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const antenna = await Antennas.findOne({
 		id: ps.antennaId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts
index bb5891261..6910c0729 100644
--- a/packages/backend/src/server/api/endpoints/antennas/list.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/list.ts
@@ -19,8 +19,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const antennas = await Antennas.find({
 		userId: me.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index eb7de901c..283c01c2c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import readNote from '@/services/note/read';
 import { Antennas, Notes, AntennaNotes } from '@/models/index';
@@ -16,33 +14,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		antennaId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-	},
-
 	errors: {
 		noSuchAntenna: {
 			message: 'No such antenna.',
@@ -62,8 +33,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		antennaId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: ['antennaId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const antenna = await Antennas.findOne({
 		id: ps.antennaId,
 		userId: user.id,
@@ -92,7 +76,7 @@ export default define(meta, async (ps, user) => {
 	generateBlockedUserQuery(query, user);
 
 	const notes = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	if (notes.length > 0) {
diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts
index a37d37d31..d8c08f277 100644
--- a/packages/backend/src/server/api/endpoints/antennas/show.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Antennas } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		antennaId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchAntenna: {
 			message: 'No such antenna.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		antennaId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['antennaId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the antenna
 	const antenna = await Antennas.findOne({
 		id: ps.antennaId,
diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts
index 900f72550..9f8564c2c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/update.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Antennas, UserLists, UserGroupJoinings } from '@/models/index';
@@ -12,56 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		antennaId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.str.range(1, 100),
-		},
-
-		src: {
-			validator: $.str.or(['home', 'all', 'users', 'list', 'group']),
-		},
-
-		userListId: {
-			validator: $.nullable.optional.type(ID),
-		},
-
-		userGroupId: {
-			validator: $.nullable.optional.type(ID),
-		},
-
-		keywords: {
-			validator: $.arr($.arr($.str)),
-		},
-
-		excludeKeywords: {
-			validator: $.arr($.arr($.str)),
-		},
-
-		users: {
-			validator: $.arr($.str),
-		},
-
-		caseSensitive: {
-			validator: $.bool,
-		},
-
-		withReplies: {
-			validator: $.bool,
-		},
-
-		withFile: {
-			validator: $.bool,
-		},
-
-		notify: {
-			validator: $.bool,
-		},
-	},
-
 	errors: {
 		noSuchAntenna: {
 			message: 'No such antenna.',
@@ -89,8 +37,37 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		antennaId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
+		userListId: { type: 'string', format: 'misskey:id', nullable: true },
+		userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
+		keywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		excludeKeywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		users: { type: 'array', items: {
+			type: 'string',
+		} },
+		caseSensitive: { type: 'boolean' },
+		withReplies: { type: 'boolean' },
+		withFile: { type: 'boolean' },
+		notify: { type: 'boolean' },
+	},
+	required: ['antennaId', 'name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the antenna
 	const antenna = await Antennas.findOne({
 		id: ps.antennaId,
diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts
index ff8c677b9..c62ca6088 100644
--- a/packages/backend/src/server/api/endpoints/ap/get.ts
+++ b/packages/backend/src/server/api/endpoints/ap/get.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import Resolver from '@/remote/activitypub/resolver';
 import { ApiError } from '../../error';
@@ -14,12 +13,6 @@ export const meta = {
 		max: 30,
 	},
 
-	params: {
-		uri: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 	},
 
@@ -29,8 +22,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		uri: { type: 'string' },
+	},
+	required: ['uri'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const resolver = new Resolver();
 	const object = await resolver.resolve(ps.uri);
 	return object;
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 7d17d8edc..e0b53403b 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import config from '@/config/index';
 import { createPerson } from '@/remote/activitypub/models/person';
@@ -24,12 +23,6 @@ export const meta = {
 		max: 30,
 	},
 
-	params: {
-		uri: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 		noSuchObject: {
 			message: 'No such object.',
@@ -75,8 +68,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		uri: { type: 'string' },
+	},
+	required: ['uri'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const object = await fetchAny(ps.uri);
 	if (object) {
 		return object;
diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts
index fbe6690f1..0f9610d2a 100644
--- a/packages/backend/src/server/api/endpoints/app/create.ts
+++ b/packages/backend/src/server/api/endpoints/app/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Apps } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -10,26 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		name: {
-			validator: $.str,
-		},
-
-		description: {
-			validator: $.str,
-		},
-
-		permission: {
-			validator: $.arr($.str).unique(),
-		},
-
-		// TODO: Check it is valid url
-		callbackUrl: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -37,8 +16,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string' },
+		description: { type: 'string' },
+		permission: { type: 'array', uniqueItems: true, items: {
+			type: 'string',
+		} },
+		callbackUrl: { type: 'string', nullable: true },
+	},
+	required: ['name', 'description', 'permission'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Generate secret
 	const secret = secureRndstr(32, true);
 
diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts
index 9f4777b38..0ca7fcc2a 100644
--- a/packages/backend/src/server/api/endpoints/app/show.ts
+++ b/packages/backend/src/server/api/endpoints/app/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Apps } from '@/models/index';
@@ -7,12 +5,6 @@ import { Apps } from '@/models/index';
 export const meta = {
 	tags: ['app'],
 
-	params: {
-		appId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchApp: {
 			message: 'No such app.',
@@ -28,8 +20,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		appId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['appId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user, token) => {
+export default define(meta, paramDef, async (ps, user, token) => {
 	const isSecure = user != null && token == null;
 
 	// Lookup app
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index f028135ca..4429119b1 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -1,5 +1,4 @@
 import * as crypto from 'crypto';
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { AuthSessions, AccessTokens, Apps } from '@/models/index';
@@ -13,12 +12,6 @@ export const meta = {
 
 	secure: true,
 
-	params: {
-		token: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 		noSuchSession: {
 			message: 'No such session.',
@@ -28,8 +21,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		token: { type: 'string' },
+	},
+	required: ['token'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch token
 	const session = await AuthSessions
 		.findOne({ token: ps.token });
diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
index 98987eba5..8d5c14e2f 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
@@ -1,5 +1,4 @@
 import { v4 as uuid } from 'uuid';
-import $ from 'cafy';
 import config from '@/config/index';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -11,12 +10,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		appSecret: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -42,8 +35,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		appSecret: { type: 'string' },
+	},
+	required: ['appSecret'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	// Lookup app
 	const app = await Apps.findOne({
 		secret: ps.appSecret,
diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts
index ae0d016ce..dfc1c06ee 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/show.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { AuthSessions } from '@/models/index';
@@ -8,12 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		token: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 		noSuchSession: {
 			message: 'No such session.',
@@ -44,8 +37,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		token: { type: 'string' },
+	},
+	required: ['token'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Lookup session
 	const session = await AuthSessions.findOne({
 		token: ps.token,
diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
index fe0211ebe..397d1e687 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index';
@@ -8,16 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		appSecret: {
-			validator: $.str,
-		},
-
-		token: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -56,8 +45,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		appSecret: { type: 'string' },
+		token: { type: 'string' },
+	},
+	required: ['appSecret', 'token'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	// Lookup app
 	const app = await Apps.findOne({
 		secret: ps.appSecret,
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index 6d555ff56..8fd8f3333 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import ms from 'ms';
 import create from '@/services/blocking/create';
 import define from '../../define';
@@ -19,12 +17,6 @@ export const meta = {
 
 	kind: 'write:blocks',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -52,8 +44,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const blocker = await Users.findOneOrFail(user.id);
 
 	// 自分自身
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index 942cddaed..ad00fd1e0 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import ms from 'ms';
 import deleteBlocking from '@/services/blocking/delete';
 import define from '../../define';
@@ -19,12 +17,6 @@ export const meta = {
 
 	kind: 'write:blocks',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -52,8 +44,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const blocker = await Users.findOneOrFail(user.id);
 
 	// Check if the blockee is yourself
diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts
index 9a4f66214..a315ccde9 100644
--- a/packages/backend/src/server/api/endpoints/blocking/list.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/list.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Blockings } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:blocks',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 30,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId)
 		.andWhere(`blocking.blockerId = :meId`, { meId: me.id });
 
 	const blockings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Blockings.packMany(blockings, me);
diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts
index 68cdf1143..dc7afadfa 100644
--- a/packages/backend/src/server/api/endpoints/channels/create.ts
+++ b/packages/backend/src/server/api/endpoints/channels/create.ts
@@ -1,10 +1,8 @@
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Channels, DriveFiles } from '@/models/index';
 import { Channel } from '@/models/entities/channel';
 import { genId } from '@/misc/gen-id';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['channels'],
@@ -13,20 +11,6 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	params: {
-		name: {
-			validator: $.str.range(1, 128),
-		},
-
-		description: {
-			validator: $.nullable.optional.str.range(1, 2048),
-		},
-
-		bannerId: {
-			validator: $.nullable.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -42,8 +26,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 128 },
+		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let banner = null;
 	if (ps.bannerId != null) {
 		banner = await DriveFiles.findOne({
diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts
index ceadde907..4d07e720a 100644
--- a/packages/backend/src/server/api/endpoints/channels/featured.ts
+++ b/packages/backend/src/server/api/endpoints/channels/featured.ts
@@ -17,8 +17,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Channels.createQueryBuilder('channel')
 		.where('channel.lastNotedAt IS NOT NULL')
 		.orderBy('channel.lastNotedAt', 'DESC');
diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts
index bf580eea6..01156d1c2 100644
--- a/packages/backend/src/server/api/endpoints/channels/follow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/follow.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Channels, ChannelFollowings } from '@/models/index';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	params: {
-		channelId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchChannel: {
 			message: 'No such channel.',
@@ -28,8 +20,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['channelId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const channel = await Channels.findOne({
 		id: ps.channelId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts
index 9e4c942af..0e75f06f4 100644
--- a/packages/backend/src/server/api/endpoints/channels/followed.ts
+++ b/packages/backend/src/server/api/endpoints/channels/followed.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Channels, ChannelFollowings } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:channels',
 
-	params: {
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 5,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId)
 		.andWhere({ followerId: me.id });
 
 	const followings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me)));
diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts
index 5473636a8..f8acae836 100644
--- a/packages/backend/src/server/api/endpoints/channels/owned.ts
+++ b/packages/backend/src/server/api/endpoints/channels/owned.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Channels } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:channels',
 
-	params: {
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 5,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId)
 		.andWhere({ userId: me.id });
 
 	const channels = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Promise.all(channels.map(x => Channels.pack(x, me)));
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 598a87ec4..fb2c7b5ad 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Channels } from '@/models/index';
@@ -9,12 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		channelId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -30,8 +22,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['channelId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const channel = await Channels.findOne({
 		id: ps.channelId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 2639095f8..a870ecccb 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Notes, Channels } from '@/models/index';
@@ -11,33 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		channelId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -57,8 +28,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: ['channelId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const channel = await Channels.findOne({
 		id: ps.channelId,
 	});
@@ -78,7 +62,7 @@ export default define(meta, async (ps, user) => {
 		.leftJoinAndSelect('note.channel', 'channel');
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	if (user) activeUsersChart.read(user);
 
diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
index ada0cb29f..42a0eb0a5 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Channels, ChannelFollowings } from '@/models/index';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	params: {
-		channelId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchChannel: {
 			message: 'No such channel.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['channelId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const channel = await Channels.findOne({
 		id: ps.channelId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index 1f7108a1c..5a593c62e 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Channels, DriveFiles } from '@/models/index';
@@ -11,24 +9,6 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	params: {
-		channelId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.optional.str.range(1, 128),
-		},
-
-		description: {
-			validator: $.nullable.optional.str.range(1, 2048),
-		},
-
-		bannerId: {
-			validator: $.nullable.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -56,8 +36,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', minLength: 1, maxLength: 128 },
+		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: ['channelId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const channel = await Channels.findOne({
 		id: ps.channelId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts
index a51cd0035..a99269da0 100644
--- a/packages/backend/src/server/api/endpoints/charts/active-users.ts
+++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { activeUsersChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'users'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(activeUsersChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await activeUsersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/ap-request.ts b/packages/backend/src/server/api/endpoints/charts/ap-request.ts
index 38bbddb27..403a93380 100644
--- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts
+++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { apRequestChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(apRequestChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await apRequestChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts
index 4bbb9861f..2c815367f 100644
--- a/packages/backend/src/server/api/endpoints/charts/drive.ts
+++ b/packages/backend/src/server/api/endpoints/charts/drive.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { driveChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'drive'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(driveChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await driveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts
index 237678ffe..c48613ffa 100644
--- a/packages/backend/src/server/api/endpoints/charts/federation.ts
+++ b/packages/backend/src/server/api/endpoints/charts/federation.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { federationChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(federationChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await federationChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts
index 6c12cb063..3cb0784ba 100644
--- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts
+++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts
@@ -1,35 +1,25 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { hashtagChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'hashtags'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(hashtagChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		tag: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		tag: { type: 'string' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'tag'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.tag);
+export default define(meta, paramDef, async (ps) => {
+	return await hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts
index 32a10d5a2..2da3cd3c3 100644
--- a/packages/backend/src/server/api/endpoints/charts/instance.ts
+++ b/packages/backend/src/server/api/endpoints/charts/instance.ts
@@ -1,35 +1,25 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { instanceChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(instanceChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		host: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		host: { type: 'string' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'host'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await instanceChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.host);
+export default define(meta, paramDef, async (ps) => {
+	return await instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts
index 09255f1d2..c48bc67e1 100644
--- a/packages/backend/src/server/api/endpoints/charts/notes.ts
+++ b/packages/backend/src/server/api/endpoints/charts/notes.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { notesChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'notes'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(notesChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await notesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts
index 89379858d..62f7afa43 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts
@@ -1,36 +1,25 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { perUserDriveChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'drive', 'users'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(perUserDriveChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		userId: { type: 'string', format: 'misskey:id' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
+export default define(meta, paramDef, async (ps) => {
+	return await perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts
index 26c3f5c12..d6d2d5e3e 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/following.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts
@@ -1,36 +1,25 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { perUserFollowingChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'users', 'following'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(perUserFollowingChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		userId: { type: 'string', format: 'misskey:id' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
+export default define(meta, paramDef, async (ps) => {
+	return await perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts
index ecf85becf..becde8b1d 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts
@@ -1,36 +1,25 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { perUserNotesChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'users', 'notes'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(perUserNotesChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		userId: { type: 'string', format: 'misskey:id' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
+export default define(meta, paramDef, async (ps) => {
+	return await perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
index 6e08d0751..8f4d9a812 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
@@ -1,36 +1,25 @@
-import $ from 'cafy';
 import define from '../../../define';
-import { ID } from '@/misc/cafy-id';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { perUserReactionsChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'users', 'reactions'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(perUserReactionsChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
+		userId: { type: 'string', format: 'misskey:id' },
 	},
-
-	// TODO: response definition
+	required: ['span', 'userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
+export default define(meta, paramDef, async (ps) => {
+	return await perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
 });
diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts
index 15a9ec038..855fc6cd0 100644
--- a/packages/backend/src/server/api/endpoints/charts/users.ts
+++ b/packages/backend/src/server/api/endpoints/charts/users.ts
@@ -1,31 +1,24 @@
-import $ from 'cafy';
 import define from '../../define';
-import { convertLog } from '@/services/chart/core';
+import { getJsonSchema } from '@/services/chart/core';
 import { usersChart } from '@/services/chart/index';
 
 export const meta = {
 	tags: ['charts', 'users'],
 
-	params: {
-		span: {
-			validator: $.str.or(['day', 'hour']),
-		},
+	res: getJsonSchema(usersChart.schema),
+} as const;
 
-		limit: {
-			validator: $.optional.num.range(1, 500),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.nullable.num,
-			default: null,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		span: { type: 'string', enum: ['day', 'hour'] },
+		limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 },
+		offset: { type: 'integer', nullable: true, default: null },
 	},
-
-	// TODO: response definition
+	required: ['span'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
-	return await usersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
+export default define(meta, paramDef, async (ps) => {
+	return await usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
 });
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index 4a740b6cf..e7dc25fe4 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ClipNotes, Clips } from '@/models/index';
 import { ApiError } from '../../error';
@@ -13,16 +11,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		clipId: {
-			validator: $.type(ID),
-		},
-
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchClip: {
 			message: 'No such clip.',
@@ -44,8 +32,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		clipId: { type: 'string', format: 'misskey:id' },
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['clipId', 'noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const clip = await Clips.findOne({
 		id: ps.clipId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index 852e66c9e..b6ebf866b 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { genId } from '@/misc/gen-id';
 import { Clips } from '@/models/index';
@@ -10,20 +9,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		name: {
-			validator: $.str.range(1, 100),
-		},
-
-		isPublic: {
-			validator: $.optional.bool,
-		},
-
-		description: {
-			validator: $.optional.nullable.str.range(1, 2048),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -31,8 +16,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		isPublic: { type: 'boolean' },
+		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const clip = await Clips.insert({
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts
index 85c64a115..a263e1052 100644
--- a/packages/backend/src/server/api/endpoints/clips/delete.ts
+++ b/packages/backend/src/server/api/endpoints/clips/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Clips } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		clipId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchClip: {
 			message: 'No such clip.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		clipId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['clipId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const clip = await Clips.findOne({
 		id: ps.clipId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts
index d88897d16..959b60e6f 100644
--- a/packages/backend/src/server/api/endpoints/clips/list.ts
+++ b/packages/backend/src/server/api/endpoints/clips/list.ts
@@ -19,8 +19,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const clips = await Clips.find({
 		userId: me.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index eeb20631c..dd76e396b 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ClipNotes, Clips, Notes } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -15,25 +13,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		clipId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchClip: {
 			message: 'No such clip.',
@@ -53,8 +32,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		clipId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['clipId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const clip = await Clips.findOne({
 		id: ps.clipId,
 	});
@@ -87,7 +77,7 @@ export default define(meta, async (ps, user) => {
 	}
 
 	const notes = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Notes.packMany(notes, user);
diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts
index 0a4567201..7fe827a96 100644
--- a/packages/backend/src/server/api/endpoints/clips/show.ts
+++ b/packages/backend/src/server/api/endpoints/clips/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Clips } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		clipId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchClip: {
 			message: 'No such clip.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		clipId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['clipId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the clip
 	const clip = await Clips.findOne({
 		id: ps.clipId,
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index 795483d5b..b86449734 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Clips } from '@/models/index';
@@ -11,24 +9,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		clipId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.str.range(1, 100),
-		},
-
-		isPublic: {
-			validator: $.optional.bool,
-		},
-
-		description: {
-			validator: $.optional.nullable.str.range(1, 2048),
-		},
-	},
-
 	errors: {
 		noSuchClip: {
 			message: 'No such clip.',
@@ -44,8 +24,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		clipId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		isPublic: { type: 'boolean' },
+		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+	},
+	required: ['clipId', 'name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the clip
 	const clip = await Clips.findOne({
 		id: ps.clipId,
diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts
index d9ab9883c..5fc1f64cf 100644
--- a/packages/backend/src/server/api/endpoints/drive.ts
+++ b/packages/backend/src/server/api/endpoints/drive.ts
@@ -25,8 +25,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const instance = await fetchMeta(true);
 
 	// Calculate drive usage
diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts
index a5c0a626a..8ca9daed4 100644
--- a/packages/backend/src/server/api/endpoints/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { DriveFiles } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,30 +9,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-
-		type: {
-			validator: $.optional.nullable.str.match(/^[a-zA-Z\/\-*]+$/),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -46,8 +20,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+		type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId)
 		.andWhere('file.userId = :userId', { userId: user.id });
 
@@ -65,7 +51,7 @@ export default define(meta, async (ps, user) => {
 		}
 	}
 
-	const files = await query.take(ps.limit!).getMany();
+	const files = await query.take(ps.limit).getMany();
 
 	return await DriveFiles.packMany(files, { detail: false, self: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
index 835dde805..20238c1b9 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { DriveFiles, Notes } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -36,8 +28,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch file
 	const file = await DriveFiles.findOne({
 		id: ps.fileId,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index a45d357ee..8b7af3e54 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { DriveFiles } from '@/models/index';
 
@@ -9,20 +8,22 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		md5: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'boolean',
 		optional: false, nullable: false,
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		md5: { type: 'string' },
+	},
+	required: ['md5'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne({
 		md5: ps.md5,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index ac7863ec7..f5791f6ec 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -1,6 +1,4 @@
 import ms from 'ms';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { addFile } from '@/services/drive/add-file';
 import define from '../../../define';
 import { apiLogger } from '../../../logger';
@@ -22,33 +20,6 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-
-		name: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		comment: {
-			validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH),
-			default: null,
-		},
-
-		isSensitive: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		force: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -64,8 +35,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+		name: { type: 'string', nullable: true, default: null },
+		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null },
+		isSensitive: { type: 'boolean', default: false },
+		force: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user, _, file, cleanup) => {
+export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
 	// Get 'name' parameter
 	let name = ps.name || file.originalname;
 	if (name !== undefined && name !== null) {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index 308beb58a..664637a68 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { deleteFile } from '@/services/drive/delete-file';
 import { publishDriveStream } from '@/services/stream';
 import define from '../../../define';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -34,8 +26,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
index dc74dcb7e..98de790c0 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { DriveFiles } from '@/models/index';
 
@@ -9,12 +8,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		md5: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -26,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		md5: { type: 'string' },
+	},
+	required: ['md5'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const files = await DriveFiles.find({
 		md5: ps.md5,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts
index 2244df13c..8d30c29da 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { DriveFiles } from '@/models/index';
 
@@ -10,17 +8,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		name: {
-			validator: $.str,
-		},
-
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -32,8 +19,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string' },
+		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const files = await DriveFiles.find({
 		name: ps.name,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index 18b17c465..8edb8785b 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { DriveFile } from '@/models/entities/drive-file';
@@ -12,16 +10,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		fileId: {
-			validator: $.optional.type(ID),
-		},
-
-		url: {
-			validator: $.optional.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -49,8 +37,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+		url: { type: 'string' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let file: DriveFile | undefined;
 
 	if (ps.fileId) {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index b7ca80e83..d77deea7d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishDriveStream } from '@/services/stream';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -13,33 +11,13 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: undefined as any,
-		},
-
-		name: {
-			validator: $.optional.str.pipe(DriveFiles.validateFileName),
-			default: undefined as any,
-		},
-
-		isSensitive: {
-			validator: $.optional.bool,
-			default: undefined as any,
-		},
-
-		comment: {
-			validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH),
-			default: undefined as any,
-		},
-	},
-
 	errors: {
+		invalidFileName: {
+			message: 'Invalid file name.',
+			code: 'INVALID_FILE_NAME',
+			id: '395e7156-f9f0-475e-af89-53c3c23080c2',
+		},
+
 		noSuchFile: {
 			message: 'No such file.',
 			code: 'NO_SUCH_FILE',
@@ -66,8 +44,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+		folderId: { type: 'string', format: 'misskey:id', nullable: true },
+		name: { type: 'string' },
+		isSensitive: { type: 'boolean' },
+		comment: { type: 'string', nullable: true, maxLength: 512 },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) {
@@ -79,6 +69,9 @@ export default define(meta, async (ps, user) => {
 	}
 
 	if (ps.name) file.name = ps.name;
+	if (!DriveFiles.validateFileName(file.name)) {
+		throw new ApiError(meta.errors.invalidFileName);
+	}
 
 	if (ps.comment !== undefined) file.comment = ps.comment;
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index 40da1a4fb..a4619ebf3 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import ms from 'ms';
 import { uploadFromUrl } from '@/services/drive/upload-from-url';
 import define from '../../../define';
@@ -18,42 +16,23 @@ export const meta = {
 	requireCredential: true,
 
 	kind: 'write:drive',
+} as const;
 
-	params: {
-		url: {
-			// TODO: Validate this url
-			validator: $.str,
-		},
-
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-
-		isSensitive: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		comment: {
-			validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH),
-			default: null,
-		},
-
-		marker: {
-			validator: $.optional.nullable.str,
-			default: null,
-		},
-
-		force: {
-			validator: $.optional.bool,
-			default: false,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		url: { type: 'string' },
+		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+		isSensitive: { type: 'boolean', default: false },
+		comment: { type: 'string', nullable: true, maxLength: 512, default: null },
+		marker: { type: 'string', nullable: true, default: null },
+		force: { type: 'boolean', default: false },
 	},
+	required: ['url'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => {
 		DriveFiles.pack(file, { self: true }).then(packedFile => {
 			publishMainStream(user.id, 'urlUploadFinished', {
diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts
index 8f8d1d2c0..a543ae27d 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { DriveFolders } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,26 +9,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		folderId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -42,8 +20,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId)
 		.andWhere('folder.userId = :userId', { userId: user.id });
 
@@ -53,7 +42,7 @@ export default define(meta, async (ps, user) => {
 		query.andWhere('folder.parentId IS NULL');
 	}
 
-	const folders = await query.take(ps.limit!).getMany();
+	const folders = await query.take(ps.limit).getMany();
 
 	return await Promise.all(folders.map(folder => DriveFolders.pack(folder)));
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
index 38ed17e0e..ab48d5599 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishDriveStream } from '@/services/stream';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -13,17 +11,6 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		name: {
-			validator: $.optional.str.pipe(DriveFolders.validateFolderName),
-			default: 'Untitled',
-		},
-
-		parentId: {
-			validator: $.optional.nullable.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFolder: {
 			message: 'No such folder.',
@@ -39,8 +26,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', default: "Untitled", maxLength: 200 },
+		parentId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// If the parent folder is specified
 	let parent = null;
 	if (ps.parentId) {
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
index 13716fcce..51c034052 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { publishDriveStream } from '@/services/stream';
 import { ApiError } from '../../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		folderId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFolder: {
 			message: 'No such folder.',
@@ -33,8 +25,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		folderId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['folderId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Get folder
 	const folder = await DriveFolders.findOne({
 		id: ps.folderId,
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts
index 911f51d78..057f40b1c 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { DriveFolders } from '@/models/index';
 
@@ -10,17 +8,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		name: {
-			validator: $.str,
-		},
-
-		parentId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -32,8 +19,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string' },
+		parentId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const folders = await DriveFolders.find({
 		name: ps.name,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
index 58a6dd3c0..d31d7a427 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { DriveFolders } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		folderId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		folderId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['folderId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Get folder
 	const folder = await DriveFolders.findOne({
 		id: ps.folderId,
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
index 5b0cccd1c..c70b5a0d5 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishDriveStream } from '@/services/stream';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -12,20 +10,6 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	params: {
-		folderId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.optional.str.pipe(DriveFolders.validateFolderName),
-		},
-
-		parentId: {
-			validator: $.optional.nullable.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFolder: {
 			message: 'No such folder.',
@@ -53,8 +37,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		folderId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', maxLength: 200 },
+		parentId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: ['folderId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch folder
 	const folder = await DriveFolders.findOne({
 		id: ps.folderId,
diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts
index 9ba780494..921ae4c22 100644
--- a/packages/backend/src/server/api/endpoints/drive/stream.ts
+++ b/packages/backend/src/server/api/endpoints/drive/stream.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { DriveFiles } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,25 +9,6 @@ export const meta = {
 
 	kind: 'read:drive',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		type: {
-			validator: $.optional.str.match(/^[a-zA-Z\/\-*]+$/),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -41,8 +20,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		type: { type: 'string', pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId)
 		.andWhere('file.userId = :userId', { userId: user.id });
 
@@ -54,7 +44,7 @@ export default define(meta, async (ps, user) => {
 		}
 	}
 
-	const files = await query.take(ps.limit!).getMany();
+	const files = await query.take(ps.limit).getMany();
 
 	return await DriveFiles.packMany(files, { detail: false, self: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts
index 19f9b7ccd..cb740ffae 100644
--- a/packages/backend/src/server/api/endpoints/email-address/available.ts
+++ b/packages/backend/src/server/api/endpoints/email-address/available.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { validateEmailForAccount } from '@/services/validate-email-for-account';
 
@@ -7,12 +6,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		emailAddress: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -29,7 +22,15 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		emailAddress: { type: 'string' },
+	},
+	required: ['emailAddress'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	return await validateEmailForAccount(ps.emailAddress);
 });
diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts
index 42fd46883..b8fe030e8 100644
--- a/packages/backend/src/server/api/endpoints/endpoint.ts
+++ b/packages/backend/src/server/api/endpoints/endpoint.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../define';
 import endpoints from '../endpoints';
 
@@ -6,16 +5,18 @@ export const meta = {
 	requireCredential: false,
 
 	tags: ['meta'],
+} as const;
 
-	params: {
-		endpoint: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		endpoint: { type: 'string' },
 	},
+	required: ['endpoint'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const ep = endpoints.find(x => x.name === ps.endpoint);
 	if (ep == null) return null;
 	return {
diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts
index ebb78de33..b090e0dce 100644
--- a/packages/backend/src/server/api/endpoints/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints/endpoints.ts
@@ -6,9 +6,6 @@ export const meta = {
 
 	tags: ['meta'],
 
-	params: {
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -25,7 +22,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	return endpoints.map(x => x.name);
 });
diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
index 24c9f56aa..f0ab63f74 100644
--- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
+++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../define';
 import { createExportCustomEmojisJob } from '@/queue/index';
 import ms from 'ms';
@@ -12,7 +11,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportCustomEmojisJob(user);
 });
diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts
index c0a85f166..5960e482c 100644
--- a/packages/backend/src/server/api/endpoints/federation/followers.ts
+++ b/packages/backend/src/server/api/endpoints/federation/followers.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Followings } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -9,25 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		host: {
-			validator: $.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -39,13 +18,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: ['host'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
 		.andWhere(`following.followeeHost = :host`, { host: ps.host });
 
 	const followings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Followings.packMany(followings, me, { populateFollowee: true });
diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts
index 147f0aedb..4f139b0c4 100644
--- a/packages/backend/src/server/api/endpoints/federation/following.ts
+++ b/packages/backend/src/server/api/endpoints/federation/following.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Followings } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -9,25 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		host: {
-			validator: $.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -39,13 +18,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: ['host'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
 		.andWhere(`following.followerHost = :host`, { host: ps.host });
 
 	const followings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Followings.packMany(followings, me, { populateFollowee: true });
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 11df7ed6b..494b5120a 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import config from '@/config/index';
 import define from '../../define';
 import { Instances } from '@/models/index';
@@ -9,50 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		host: {
-			validator: $.optional.nullable.str,
-		},
-
-		blocked: {
-			validator: $.optional.nullable.bool,
-		},
-
-		notResponding: {
-			validator: $.optional.nullable.bool,
-		},
-
-		suspended: {
-			validator: $.optional.nullable.bool,
-		},
-
-		federating: {
-			validator: $.optional.nullable.bool,
-		},
-
-		subscribing: {
-			validator: $.optional.nullable.bool,
-		},
-
-		publishing: {
-			validator: $.optional.nullable.bool,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 30,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-
-		sort: {
-			validator: $.optional.str,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -64,8 +19,25 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string', nullable: true },
+		blocked: { type: 'boolean', nullable: true },
+		notResponding: { type: 'boolean', nullable: true },
+		suspended: { type: 'boolean', nullable: true },
+		federating: { type: 'boolean', nullable: true },
+		subscribing: { type: 'boolean', nullable: true },
+		publishing: { type: 'boolean', nullable: true },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+		offset: { type: 'integer', default: 0 },
+		sort: { type: 'string' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Instances.createQueryBuilder('instance');
 
 	switch (ps.sort) {
@@ -144,7 +116,7 @@ export default define(meta, async (ps, me) => {
 		query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' });
 	}
 
-	const instances = await query.take(ps.limit!).skip(ps.offset).getMany();
+	const instances = await query.take(ps.limit).skip(ps.offset).getMany();
 
-	return instances;
+	return await Instances.packMany(instances);
 });
diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
index 6f13b28ca..cead515d8 100644
--- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts
+++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Instances } from '@/models/index';
 import { toPuny } from '@/misc/convert-host';
@@ -8,23 +7,28 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		host: {
-			validator: $.str,
-		},
-	},
-
 	res: {
-		type: 'object',
-		optional: true, nullable: false,
-		ref: 'FederationInstance',
+		oneOf: [{
+			type: 'object',
+			ref: 'FederationInstance',
+		}, {
+			type: 'null',
+		}],
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
+	},
+	required: ['host'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const instance = await Instances
 		.findOne({ host: toPuny(ps.host) });
 
-	return instance;
+	return instance ? await Instances.pack(instance) : null;
 });
diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
index 092f805bc..ebdc54d0a 100644
--- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
+++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { getRemoteUser } from '../../common/getters';
 import { updatePerson } from '@/remote/activitypub/models/person';
@@ -8,16 +6,18 @@ export const meta = {
 	tags: ['federation'],
 
 	requireCredential: true,
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const user = await getRemoteUser(ps.userId);
 	await updatePerson(user.uri!);
 });
diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts
index 9a8f74993..294894cd9 100644
--- a/packages/backend/src/server/api/endpoints/federation/users.ts
+++ b/packages/backend/src/server/api/endpoints/federation/users.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Users } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -9,25 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		host: {
-			validator: $.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -39,13 +18,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		host: { type: 'string' },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: ['host'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId)
 		.andWhere(`user.host = :host`, { host: ps.host });
 
 	const users = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Users.packMany(users, me, { detail: true });
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index b840ab769..1edb3517a 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import ms from 'ms';
 import create from '@/services/following/create';
 import define from '../../define';
@@ -20,12 +18,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -65,8 +57,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const follower = user;
 
 	// 自分自身
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index 4cd0c4945..2eb863681 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import ms from 'ms';
 import deleteFollowing from '@/services/following/delete';
 import define from '../../define';
@@ -19,12 +17,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -52,8 +44,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const follower = user;
 
 	// Check if the followee is yourself
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index 92e887e00..8a1da3d29 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import * as ms from 'ms';
 import deleteFollowing from '@/services/following/delete';
 import define from '../../define';
@@ -19,12 +17,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -52,8 +44,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const followee = user;
 
 	// Check if the follower is yourself
diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
index 7e7c056f5..7040efcc8 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import acceptFollowRequest from '@/services/following/requests/accept';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch follower
 	const follower = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
index c9abbf36d..4204f8526 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import cancelFollowRequest from '@/services/following/requests/cancel';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -14,12 +12,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -41,8 +33,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch followee
 	const followee = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts
index ec0c76502..17d576d1c 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/list.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts
@@ -35,8 +35,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const reqs = await FollowRequests.find({
 		followeeId: user.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
index a5ce1e7c7..9e345aa15 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { rejectFollowRequest } from '@/services/following/reject';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:following',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch follower
 	const follower = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts
index ff7c16889..6b347f9de 100644
--- a/packages/backend/src/server/api/endpoints/gallery/featured.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts
@@ -17,8 +17,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = GalleryPosts.createQueryBuilder('post')
 		.andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) })
 		.andWhere('post.likedCount > 0')
diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts
index 2c3368a19..57ead7982 100644
--- a/packages/backend/src/server/api/endpoints/gallery/popular.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts
@@ -17,8 +17,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = GalleryPosts.createQueryBuilder('post')
 		.andWhere('post.likedCount > 0')
 		.orderBy('post.likedCount', 'DESC');
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts
index 9d2601c7e..c36e80b83 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { GalleryPosts } from '@/models/index';
@@ -7,21 +5,6 @@ import { GalleryPosts } from '@/models/index';
 export const meta = {
 	tags: ['gallery'],
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -33,12 +16,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId)
 		.innerJoinAndSelect('post.user', 'user');
 
-	const posts = await query.take(ps.limit!).getMany();
+	const posts = await query.take(ps.limit).getMany();
 
 	return await GalleryPosts.packMany(posts, me);
 });
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index e9d5df1ab..d64937d0b 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -1,7 +1,5 @@
-import $ from 'cafy';
 import ms from 'ms';
 import define from '../../../define';
-import { ID } from '../../../../../misc/cafy-id';
 import { DriveFiles, GalleryPosts } from '@/models/index';
 import { genId } from '../../../../../misc/gen-id';
 import { GalleryPost } from '@/models/entities/gallery-post';
@@ -20,25 +18,6 @@ export const meta = {
 		max: 300,
 	},
 
-	params: {
-		title: {
-			validator: $.str.min(1),
-		},
-
-		description: {
-			validator: $.optional.nullable.str,
-		},
-
-		fileIds: {
-			validator: $.arr($.type(ID)).unique().range(1, 32),
-		},
-
-		isSensitive: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -50,8 +29,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		title: { type: 'string', minLength: 1 },
+		description: { type: 'string', nullable: true },
+		fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 32, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		isSensitive: { type: 'boolean', default: false },
+	},
+	required: ['title', 'fileIds'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const files = (await Promise.all(ps.fileIds.map(fileId =>
 		DriveFiles.findOne({
 			id: fileId,
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
index 2a13b9ed5..82ffd62d6 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { GalleryPosts } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['gallery'],
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:gallery',
 
-	params: {
-		postId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPost: {
 			message: 'No such post.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		postId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['postId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const post = await GalleryPosts.findOne({
 		id: ps.postId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index 0fb408fa5..73d5a0ea9 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { GalleryPosts, GalleryLikes } from '@/models/index';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:gallery-likes',
 
-	params: {
-		postId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPost: {
 			message: 'No such post.',
@@ -39,8 +31,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		postId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['postId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const post = await GalleryPosts.findOne(ps.postId);
 	if (post == null) {
 		throw new ApiError(meta.errors.noSuchPost);
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
index 4325d2ad3..819d360b1 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { GalleryPosts } from '@/models/index';
@@ -9,12 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		postId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPost: {
 			message: 'No such post.',
@@ -30,8 +22,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		postId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['postId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const post = await GalleryPosts.findOne({
 		id: ps.postId,
 	});
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
index 9cca09bdd..fc3817481 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { GalleryPosts, GalleryLikes } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:gallery-likes',
 
-	params: {
-		postId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPost: {
 			message: 'No such post.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		postId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['postId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const post = await GalleryPosts.findOne(ps.postId);
 	if (post == null) {
 		throw new ApiError(meta.errors.noSuchPost);
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index c35e1bbf5..1a86b56bc 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -1,7 +1,5 @@
-import $ from 'cafy';
 import ms from 'ms';
 import define from '../../../define';
-import { ID } from '../../../../../misc/cafy-id';
 import { DriveFiles, GalleryPosts } from '@/models/index';
 import { GalleryPost } from '@/models/entities/gallery-post';
 import { ApiError } from '../../../error';
@@ -19,29 +17,6 @@ export const meta = {
 		max: 300,
 	},
 
-	params: {
-		postId: {
-			validator: $.type(ID),
-		},
-
-		title: {
-			validator: $.str.min(1),
-		},
-
-		description: {
-			validator: $.optional.nullable.str,
-		},
-
-		fileIds: {
-			validator: $.arr($.type(ID)).unique().range(1, 32),
-		},
-
-		isSensitive: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -53,8 +28,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		postId: { type: 'string', format: 'misskey:id' },
+		title: { type: 'string', minLength: 1 },
+		description: { type: 'string', nullable: true },
+		fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 32, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		isSensitive: { type: 'boolean', default: false },
+	},
+	required: ['postId', 'title', 'fileIds'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const files = (await Promise.all(ps.fileIds.map(fileId =>
 		DriveFiles.findOne({
 			id: fileId,
diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
index 5b13d5a3b..855b9fc34 100644
--- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts
+++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
@@ -7,13 +7,16 @@ export const meta = {
 	tags: ['meta'],
 
 	requireCredential: false,
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const count = await Users.count({
 		lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)),
 	});
diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts
index 9fa9b3edc..68f2b2bf5 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/list.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Hashtags } from '@/models/index';
 
@@ -7,45 +6,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		attachedToUserOnly: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		attachedToLocalUserOnly: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		attachedToRemoteUserOnly: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		sort: {
-			validator: $.str.or([
-				'+mentionedUsers',
-				'-mentionedUsers',
-				'+mentionedLocalUsers',
-				'-mentionedLocalUsers',
-				'+mentionedRemoteUsers',
-				'-mentionedRemoteUsers',
-				'+attachedUsers',
-				'-attachedUsers',
-				'+attachedLocalUsers',
-				'-attachedLocalUsers',
-				'+attachedRemoteUsers',
-				'-attachedRemoteUsers',
-			]),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -57,8 +17,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		attachedToUserOnly: { type: 'boolean', default: false },
+		attachedToLocalUserOnly: { type: 'boolean', default: false },
+		attachedToRemoteUserOnly: { type: 'boolean', default: false },
+		sort: { type: 'string', enum: ['+mentionedUsers', '-mentionedUsers', '+mentionedLocalUsers', '-mentionedLocalUsers', '+mentionedRemoteUsers', '-mentionedRemoteUsers', '+attachedUsers', '-attachedUsers', '+attachedLocalUsers', '-attachedLocalUsers', '+attachedRemoteUsers', '-attachedRemoteUsers'] },
+	},
+	required: ['sort'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Hashtags.createQueryBuilder('tag');
 
 	if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0');
@@ -90,7 +62,7 @@ export default define(meta, async (ps, me) => {
 		'tag.attachedRemoteUsersCount',
 	]);
 
-	const tags = await query.take(ps.limit!).getMany();
+	const tags = await query.take(ps.limit).getMany();
 
 	return Hashtags.packMany(tags);
 });
diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts
index 0d646c64f..9aa08ed28 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/search.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Hashtags } from '@/models/index';
 
@@ -7,22 +6,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		query: {
-			validator: $.str,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -33,13 +16,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		query: { type: 'string' },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: ['query'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const hashtags = await Hashtags.createQueryBuilder('tag')
 		.where('tag.name like :q', { q: ps.query.toLowerCase() + '%' })
 		.orderBy('tag.count', 'DESC')
 		.groupBy('tag.id')
-		.take(ps.limit!)
+		.take(ps.limit)
 		.skip(ps.offset)
 		.getMany();
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts
index 242cef99d..79b961308 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/show.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Hashtags } from '@/models/index';
@@ -9,12 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		tag: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -30,8 +23,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		tag: { type: 'string' },
+	},
+	required: ['tag'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const hashtag = await Hashtags.findOne({ name: normalizeForSearch(ps.tag) });
 	if (hashtag == null) {
 		throw new ApiError(meta.errors.noSuchHashtag);
diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
index be964ad63..de3e453c3 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
@@ -53,8 +53,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const instance = await fetchMeta(true);
 	const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts
index 2158dc434..1760732c1 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/users.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Users } from '@/models/index';
 import { normalizeForSearch } from '@/misc/normalize-for-search';
@@ -8,45 +7,6 @@ export const meta = {
 
 	tags: ['hashtags', 'users'],
 
-	params: {
-		tag: {
-			validator: $.str,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sort: {
-			validator: $.str.or([
-				'+follower',
-				'-follower',
-				'+createdAt',
-				'-createdAt',
-				'+updatedAt',
-				'-updatedAt',
-			]),
-		},
-
-		state: {
-			validator: $.optional.str.or([
-				'all',
-				'alive',
-			]),
-			default: 'all',
-		},
-
-		origin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'local',
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -58,8 +18,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		tag: { type: 'string' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
+		state: { type: 'string', enum: ['all', 'alive'], default: "all" },
+		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
+	},
+	required: ['tag', 'sort'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Users.createQueryBuilder('user')
 		.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) });
 
@@ -84,7 +56,7 @@ export default define(meta, async (ps, me) => {
 		case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break;
 	}
 
-	const users = await query.take(ps.limit!).getMany();
+	const users = await query.take(ps.limit).getMany();
 
 	return await Users.packMany(users, me, { detail: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts
index d69c118cf..091276ea2 100644
--- a/packages/backend/src/server/api/endpoints/i.ts
+++ b/packages/backend/src/server/api/endpoints/i.ts
@@ -6,8 +6,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -15,8 +13,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user, token) => {
+export default define(meta, paramDef, async (ps, user, token) => {
 	const isSecure = token == null;
 
 	// ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
index 485390869..8d82ab388 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as speakeasy from 'speakeasy';
 import define from '../../../define';
 import { UserProfiles } from '@/models/index';
@@ -7,16 +6,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		token: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		token: { type: 'string' },
 	},
+	required: ['token'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const token = ps.token.replace(/\s/g, '');
 
 	const profile = await UserProfiles.findOneOrFail(user.id);
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 26e9a6088..5052afda9 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import { promisify } from 'util';
 import * as cbor from 'cbor';
@@ -14,35 +13,28 @@ import { procedures, hash } from '../../../2fa';
 import { publishMainStream } from '@/services/stream';
 
 const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
+const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8'));
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
-
-	params: {
-		clientDataJSON: {
-			validator: $.str,
-		},
-		attestationObject: {
-			validator: $.str,
-		},
-		password: {
-			validator: $.str,
-		},
-		challengeId: {
-			validator: $.str,
-		},
-		name: {
-			validator: $.str,
-		},
-	},
 } as const;
 
-const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8'));
+const paramDef = {
+	type: 'object',
+	properties: {
+		clientDataJSON: { type: 'string' },
+		attestationObject: { type: 'string' },
+		password: { type: 'string' },
+		challengeId: { type: 'string' },
+		name: { type: 'string' },
+	},
+	required: ['clientDataJSON', 'attestationObject', 'password', 'challengeId', 'name'],
+} as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
index 854848a43..2830f8994 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { UserProfiles } from '@/models/index';
 
@@ -6,16 +5,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		value: {
-			validator: $.boolean,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		value: { type: 'boolean' },
 	},
+	required: ['value'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	await UserProfiles.update(user.id, {
 		usePasswordLessLogin: ps.value,
 	});
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
index 057e54c69..da3ff421a 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import define from '../../../define';
 import { UserProfiles, AttestationChallenges } from '@/models/index';
@@ -13,16 +12,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
 	},
+	required: ['password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
index c5cfb9dfa..32acc838b 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import * as speakeasy from 'speakeasy';
 import * as QRCode from 'qrcode';
@@ -10,16 +9,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
 	},
+	required: ['password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
index 03e1d0434..3004ce36a 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import define from '../../../define';
 import { UserProfiles, UserSecurityKeys, Users } from '@/models/index';
@@ -8,19 +7,19 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
-		credentialId: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
+		credentialId: { type: 'string' },
 	},
+	required: ['password', 'credentialId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
index a19ad6810..b8e89bac7 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import define from '../../../define';
 import { UserProfiles } from '@/models/index';
@@ -7,16 +6,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
 	},
+	required: ['password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index 63999b098..f45b6745b 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { AccessTokens } from '@/models/index';
 
@@ -6,21 +5,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		sort: {
-			validator: $.optional.str.or([
-				'+createdAt',
-				'-createdAt',
-				'+lastUsedAt',
-				'-lastUsedAt',
-			]),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		sort: { type: 'string', enum: ['+createdAt', '-createdAt', '+lastUsedAt', '-lastUsedAt'] },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = AccessTokens.createQueryBuilder('token')
 		.where('token.userId = :userId', { userId: user.id });
 
diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
index 52122b851..e417f3e81 100644
--- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { AccessTokens, Apps } from '@/models/index';
 
@@ -6,33 +5,26 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-
-		sort: {
-			validator: $.optional.str.or('desc|asc'),
-			default: 'desc',
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+		sort: { type: 'string', enum: ['desc', 'asc'], default: "desc" },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Get tokens
 	const tokens = await AccessTokens.find({
 		where: {
 			userId: user.id,
 		},
-		take: ps.limit!,
+		take: ps.limit,
 		skip: ps.offset,
 		order: {
 			id: ps.sort == 'asc' ? 1 : -1,
diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts
index 7b6c13773..eac60ee18 100644
--- a/packages/backend/src/server/api/endpoints/i/change-password.ts
+++ b/packages/backend/src/server/api/endpoints/i/change-password.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import define from '../../define';
 import { UserProfiles } from '@/models/index';
@@ -7,20 +6,19 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		currentPassword: {
-			validator: $.str,
-		},
-
-		newPassword: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		currentPassword: { type: 'string' },
+		newPassword: { type: 'string', minLength: 1 },
 	},
+	required: ['currentPassword', 'newPassword'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts
index e1eee949f..6f689c0b4 100644
--- a/packages/backend/src/server/api/endpoints/i/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import define from '../../define';
 import { UserProfiles, Users } from '@/models/index';
@@ -10,16 +9,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
 	},
+	required: ['password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 	const userDetailed = await Users.findOneOrFail(user.id);
 	if (userDetailed.isDeleted) {
diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
index 44d8a1cb3..afa6f2fc8 100644
--- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
@@ -11,7 +11,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportBlockingJob(user);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts
index 5d1617d57..4dd8cda39 100644
--- a/packages/backend/src/server/api/endpoints/i/export-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-following.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { createExportFollowingJob } from '@/queue/index';
 import ms from 'ms';
@@ -10,19 +9,18 @@ export const meta = {
 		duration: ms('1hour'),
 		max: 1,
 	},
-	params: {
-		excludeMuting: {
-			validator: $.optional.bool,
-			default: false,
-		},
-		excludeInactive: {
-			validator: $.optional.bool,
-			default: false,
-		},
+} as const;
+
+const paramDef = {
+	type: 'object',
+	properties: {
+		excludeMuting: { type: 'boolean', default: false },
+		excludeInactive: { type: 'boolean', default: false },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts
index 27ce8f0b2..ca8a7b997 100644
--- a/packages/backend/src/server/api/endpoints/i/export-mute.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts
@@ -11,7 +11,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportMuteJob(user);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts
index 25b1849e8..fc155d9d0 100644
--- a/packages/backend/src/server/api/endpoints/i/export-notes.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts
@@ -11,7 +11,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportNotesJob(user);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
index d28b699c5..22cbf973f 100644
--- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
@@ -11,7 +11,13 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	createExportUserListsJob(user);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts
index 92c767876..45f5d99cc 100644
--- a/packages/backend/src/server/api/endpoints/i/favorites.ts
+++ b/packages/backend/src/server/api/endpoints/i/favorites.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { NoteFavorites } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:favorites',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,14 +20,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId)
 		.andWhere(`favorite.userId = :meId`, { meId: user.id })
 		.leftJoinAndSelect('favorite.note', 'note');
 
 	const favorites = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await NoteFavorites.packMany(favorites, user);
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
index f1c576359..dc862a6b0 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { GalleryLikes } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:gallery-likes',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -44,14 +27,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
 		.andWhere(`like.userId = :meId`, { meId: user.id })
 		.leftJoinAndSelect('like.post', 'post');
 
 	const likes = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await GalleryLikes.packMany(likes, user);
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
index d46d42f63..fbab8795c 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { GalleryPosts } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:gallery',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId)
 		.andWhere(`post.userId = :meId`, { meId: user.id });
 
 	const posts = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await GalleryPosts.packMany(posts, user);
diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
index 4e1a4d3db..2b31b89ff 100644
--- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
+++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
@@ -8,9 +8,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -23,8 +20,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	return {
 		count: await MutedNotes.count({
 			userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index acc579742..b90bfb140 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { createImportBlockingJob } from '@/queue/index';
 import ms from 'ms';
@@ -15,12 +13,6 @@ export const meta = {
 		max: 1,
 	},
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -48,8 +40,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index 35006746f..75388502a 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { createImportFollowingJob } from '@/queue/index';
 import ms from 'ms';
@@ -14,12 +12,6 @@ export const meta = {
 		max: 1,
 	},
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -47,8 +39,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 7bbb2e008..e1c3df33e 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { createImportMutingJob } from '@/queue/index';
 import ms from 'ms';
@@ -15,12 +13,6 @@ export const meta = {
 		max: 1,
 	},
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -48,8 +40,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 759d41b6c..eb1302a98 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { createImportUserListsJob } from '@/queue/index';
 import ms from 'ms';
@@ -14,12 +12,6 @@ export const meta = {
 		max: 1,
 	},
 
-	params: {
-		fileId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
@@ -47,8 +39,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOne(ps.fileId);
 
 	if (file == null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 59efd32bb..bb1d6a49b 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { readNotification } from '../../common/read-notification';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -16,44 +14,6 @@ export const meta = {
 
 	kind: 'read:notifications',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		following: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		unreadOnly: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		markAsRead: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeTypes: {
-			validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])),
-		},
-
-		excludeTypes: {
-			validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -65,8 +25,27 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		following: { type: 'boolean', default: false },
+		unreadOnly: { type: 'boolean', default: false },
+		markAsRead: { type: 'boolean', default: true },
+		includeTypes: { type: 'array', items: {
+			type: 'string', enum: notificationTypes,
+		} },
+		excludeTypes: { type: 'array', items: {
+			type: 'string', enum: notificationTypes,
+		} },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// includeTypes が空の場合はクエリしない
 	if (ps.includeTypes && ps.includeTypes.length === 0) {
 		return [];
@@ -125,7 +104,7 @@ export default define(meta, async (ps, user) => {
 		query.andWhere(`notification.isRead = false`);
 	}
 
-	const notifications = await query.take(ps.limit!).getMany();
+	const notifications = await query.take(ps.limit).getMany();
 
 	// Mark all as read
 	if (notifications.length > 0 && ps.markAsRead) {
diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts
index 59239c744..e66bc616c 100644
--- a/packages/backend/src/server/api/endpoints/i/page-likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { PageLikes } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:page-likes',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -44,14 +27,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
 		.andWhere(`like.userId = :meId`, { meId: user.id })
 		.leftJoinAndSelect('like.page', 'page');
 
 	const likes = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await PageLikes.packMany(likes, user);
diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts
index bef775d06..dbc58021b 100644
--- a/packages/backend/src/server/api/endpoints/i/pages.ts
+++ b/packages/backend/src/server/api/endpoints/i/pages.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Pages } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:pages',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId)
 		.andWhere(`page.userId = :meId`, { meId: user.id });
 
 	const pages = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Pages.packMany(pages);
diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts
index a940d1b99..a09618ebe 100644
--- a/packages/backend/src/server/api/endpoints/i/pin.ts
+++ b/packages/backend/src/server/api/endpoints/i/pin.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { addPinned } from '@/services/i/pin';
 import define from '../../define';
 import { ApiError } from '../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -45,8 +37,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	await addPinned(user, ps.noteId).catch(e => {
 		if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote);
 		if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded);
diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts
index 4e4fb3840..b0185a3e3 100644
--- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts
@@ -8,13 +8,16 @@ export const meta = {
 	requireCredential: true,
 
 	kind: 'write:account',
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Update documents
 	await MessagingMessages.update({
 		recipientId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
index 99f17ddfc..45638c257 100644
--- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
@@ -8,13 +8,16 @@ export const meta = {
 	requireCredential: true,
 
 	kind: 'write:account',
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Remove documents
 	await NoteUnreads.delete({
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
index e9bb66264..f3cedddb3 100644
--- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { genId } from '@/misc/gen-id';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		announcementId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchAnnouncement: {
 			message: 'No such announcement.',
@@ -28,8 +20,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		announcementId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['announcementId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Check if announcement exists
 	const announcement = await Announcements.findOne(ps.announcementId);
 
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index a20719363..71a3ea08a 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import { publishMainStream, publishUserEvent } from '@/services/stream';
 import generateUserToken from '../../common/generate-native-user-token';
@@ -9,16 +8,18 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		password: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
 	},
+	required: ['password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
index 2941b441e..0429cd283 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 
@@ -6,17 +5,20 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
index 51371353c..194a92271 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 import { ApiError } from '../../../error';
@@ -8,17 +7,6 @@ export const meta = {
 
 	secure: true,
 
-	params: {
-		key: {
-			validator: $.str,
-		},
-
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
-	},
-
 	errors: {
 		noSuchKey: {
 			message: 'No such key.',
@@ -28,8 +16,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		key: { type: 'string' },
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
+	},
+	required: ['key'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts
index ac617defb..7d413e352 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 import { ApiError } from '../../../error';
@@ -8,17 +7,6 @@ export const meta = {
 
 	secure: true,
 
-	params: {
-		key: {
-			validator: $.str,
-		},
-
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
-	},
-
 	errors: {
 		noSuchKey: {
 			message: 'No such key.',
@@ -28,8 +16,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		key: { type: 'string' },
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
+	},
+	required: ['key'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
index 044592218..52e0d6fac 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 
@@ -6,17 +5,20 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts
index a3c9d0e5e..c494ed8f5 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 
@@ -6,17 +5,20 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.select('item.key')
 		.where('item.domain IS NULL')
diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
index 08185f224..5bc55bd29 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
 import { ApiError } from '../../../error';
@@ -8,17 +7,6 @@ export const meta = {
 
 	secure: true,
 
-	params: {
-		key: {
-			validator: $.str,
-		},
-
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
-	},
-
 	errors: {
 		noSuchKey: {
 			message: 'No such key.',
@@ -28,8 +16,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		key: { type: 'string' },
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
+	},
+	required: ['key'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts
index 9de68ac6e..e04d123a1 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts
@@ -5,13 +5,16 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.select('item.scope')
 		.where('item.domain IS NULL')
diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts
index 27884046b..c78f4383b 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/set.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import { publishMainStream } from '@/services/stream';
 import define from '../../../define';
 import { RegistryItems } from '@/models/index';
@@ -8,25 +7,22 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		key: {
-			validator: $.str.min(1),
-		},
-
-		value: {
-			validator: $.nullable.any,
-		},
-
-		scope: {
-			validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
-			default: [],
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		key: { type: 'string', minLength: 1 },
+		value: {},
+		scope: { type: 'array', default: [], items: {
+			type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1),
+		} },
 	},
+	required: ['key', 'value'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = RegistryItems.createQueryBuilder('item')
 		.where('item.domain IS NULL')
 		.andWhere('item.userId = :userId', { userId: user.id })
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 51721c5b5..14800346c 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -1,23 +1,23 @@
-import $ from 'cafy';
 import define from '../../define';
 import { AccessTokens } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 import { publishUserEvent } from '@/services/stream';
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		tokenId: {
-			validator: $.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		tokenId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['tokenId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const token = await AccessTokens.findOne(ps.tokenId);
 
 	if (token) {
diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts
index 796e2ec30..468972c23 100644
--- a/packages/backend/src/server/api/endpoints/i/signin-history.ts
+++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Signins } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -8,29 +6,24 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+} as const;
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
 	},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId)
 		.andWhere(`signin.userId = :meId`, { meId: user.id });
 
-	const history = await query.take(ps.limit!).getMany();
+	const history = await query.take(ps.limit).getMany();
 
 	return await Promise.all(history.map(record => Signins.pack(record)));
 });
diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts
index 9c82b7496..895bfd0c2 100644
--- a/packages/backend/src/server/api/endpoints/i/unpin.ts
+++ b/packages/backend/src/server/api/endpoints/i/unpin.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { removePinned } from '@/services/i/pin';
 import define from '../../define';
 import { ApiError } from '../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -33,8 +25,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	await removePinned(user, ps.noteId).catch(e => {
 		if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index b4479aa50..1450eabc2 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import { publishMainStream } from '@/services/stream';
 import define from '../../define';
 import rndstr from 'rndstr';
@@ -20,16 +19,6 @@ export const meta = {
 		max: 3,
 	},
 
-	params: {
-		password: {
-			validator: $.str,
-		},
-
-		email: {
-			validator: $.optional.nullable.str,
-		},
-	},
-
 	errors: {
 		incorrectPassword: {
 			message: 'Incorrect password.',
@@ -45,8 +34,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		password: { type: 'string' },
+		email: { type: 'string', nullable: true },
+	},
+	required: ['password'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index aec7bbd2e..0a5188609 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -1,7 +1,5 @@
 const RE2 = require('re2');
-import $ from 'cafy';
 import * as mfm from 'mfm-js';
-import { ID } from '@/misc/cafy-id';
 import { publishMainStream, publishUserEvent } from '@/services/stream';
 import acceptAllFollowRequests from '@/services/following/requests/accept-all';
 import { publishToFollowers } from '@/services/i/update';
@@ -24,116 +22,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		name: {
-			validator: $.optional.nullable.use(Users.validateName),
-		},
-
-		description: {
-			validator: $.optional.nullable.use(Users.validateDescription),
-		},
-
-		lang: {
-			validator: $.optional.nullable.str.or(Object.keys(langmap)),
-		},
-
-		location: {
-			validator: $.optional.nullable.use(Users.validateLocation),
-		},
-
-		birthday: {
-			validator: $.optional.nullable.use(Users.validateBirthday),
-		},
-
-		avatarId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		bannerId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		fields: {
-			validator: $.optional.arr($.object()).range(1, 4),
-		},
-
-		isLocked: {
-			validator: $.optional.bool,
-		},
-
-		isExplorable: {
-			validator: $.optional.bool,
-		},
-
-		hideOnlineStatus: {
-			validator: $.optional.bool,
-		},
-
-		publicReactions: {
-			validator: $.optional.bool,
-		},
-
-		ffVisibility: {
-			validator: $.optional.str,
-		},
-
-		carefulBot: {
-			validator: $.optional.bool,
-		},
-
-		autoAcceptFollowed: {
-			validator: $.optional.bool,
-		},
-
-		noCrawle: {
-			validator: $.optional.bool,
-		},
-
-		isBot: {
-			validator: $.optional.bool,
-		},
-
-		isCat: {
-			validator: $.optional.bool,
-		},
-
-		showTimelineReplies: {
-			validator: $.optional.bool,
-		},
-
-		injectFeaturedNote: {
-			validator: $.optional.bool,
-		},
-
-		receiveAnnouncementEmail: {
-			validator: $.optional.bool,
-		},
-
-		alwaysMarkNsfw: {
-			validator: $.optional.bool,
-		},
-
-		pinnedPageId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		mutedWords: {
-			validator: $.optional.arr($.either($.arr($.str.min(1)).min(1), $.str)),
-		},
-
-		mutedInstances: {
-			validator: $.optional.arr($.str),
-		},
-
-		mutingNotificationTypes: {
-			validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])),
-		},
-
-		emailNotificationTypes: {
-			validator: $.optional.arr($.str),
-		},
-	},
-
 	errors: {
 		noSuchAvatar: {
 			message: 'No such avatar file.',
@@ -179,8 +67,60 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { ...Users.nameSchema, nullable: true },
+		description: { ...Users.descriptionSchema, nullable: true },
+		location: { ...Users.locationSchema, nullable: true },
+		birthday: { ...Users.birthdaySchema, nullable: true },
+		lang: { type: 'string', enum: Object.keys(langmap), nullable: true },
+		avatarId: { type: 'string', format: 'misskey:id', nullable: true },
+		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+		fields: { type: 'array',
+			minItems: 0,
+			maxItems: 8,
+			items: {
+				type: 'object',
+				properties: {
+					name: { type: 'string' },
+					value: { type: 'string' },
+				},
+				required: ['name', 'value'],
+			},
+		},
+		isLocked: { type: 'boolean' },
+		isExplorable: { type: 'boolean' },
+		hideOnlineStatus: { type: 'boolean' },
+		publicReactions: { type: 'boolean' },
+		carefulBot: { type: 'boolean' },
+		autoAcceptFollowed: { type: 'boolean' },
+		noCrawle: { type: 'boolean' },
+		isBot: { type: 'boolean' },
+		isCat: { type: 'boolean' },
+		showTimelineReplies: { type: 'boolean' },
+		injectFeaturedNote: { type: 'boolean' },
+		receiveAnnouncementEmail: { type: 'boolean' },
+		alwaysMarkNsfw: { type: 'boolean' },
+		ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
+		pinnedPageId: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		mutedWords: { type: 'array' },
+		mutedInstances: { type: 'array', items: {
+			type: 'string',
+		} },
+		mutingNotificationTypes: { type: 'array', items: {
+			type: 'string', enum: notificationTypes,
+		} },
+		emailNotificationTypes: { type: 'array', items: {
+			type: 'string',
+		} },
+	},
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, _user, token) => {
+export default define(meta, paramDef, async (ps, _user, token) => {
 	const user = await Users.findOneOrFail(_user.id);
 	const isSecure = token == null;
 
diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts
index 76a3131e6..6c3824091 100644
--- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts
+++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { UserGroupInvitations } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:user-groups',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -48,14 +31,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId)
 		.andWhere(`invitation.userId = :meId`, { meId: user.id })
 		.leftJoinAndSelect('invitation.userGroup', 'user_group');
 
 	const invitations = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await UserGroupInvitations.packMany(invitations);
diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts
index 5ac49cf96..5c1c040cc 100644
--- a/packages/backend/src/server/api/endpoints/messaging/history.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/history.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { MessagingMessage } from '@/models/entities/messaging-message';
 import { MessagingMessages, Mutings, UserGroupJoinings } from '@/models/index';
@@ -11,18 +10,6 @@ export const meta = {
 
 	kind: 'read:messaging',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		group: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -34,8 +21,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		group: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const mute = await Mutings.find({
 		muterId: user.id,
 	});
@@ -50,7 +46,7 @@ export default define(meta, async (ps, user) => {
 
 	const history: MessagingMessage[] = [];
 
-	for (let i = 0; i < ps.limit!; i++) {
+	for (let i = 0; i < ps.limit; i++) {
 		const found = ps.group
 			? history.map(m => m.groupId!)
 			: history.map(m => (m.userId === user.id) ? m.recipientId! : m.userId!);
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts
index 7dbddd80e..ce477feac 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getUser } from '../../common/getters';
@@ -15,34 +13,6 @@ export const meta = {
 
 	kind: 'read:messaging',
 
-	params: {
-		userId: {
-			validator: $.optional.type(ID),
-		},
-
-		groupId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		markAsRead: {
-			validator: $.optional.bool,
-			default: true,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -74,8 +44,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		groupId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		markAsRead: { type: 'boolean', default: true },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	if (ps.userId != null) {
 		// Fetch recipient (user)
 		const recipient = await getUser(ps.userId).catch(e => {
@@ -97,7 +80,7 @@ export default define(meta, async (ps, user) => {
 			.setParameter('meId', user.id)
 			.setParameter('recipientId', recipient.id);
 
-		const messages = await query.take(ps.limit!).getMany();
+		const messages = await query.take(ps.limit).getMany();
 
 		// Mark all as read
 		if (ps.markAsRead) {
@@ -133,7 +116,7 @@ export default define(meta, async (ps, user) => {
 		const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
 			.andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id });
 
-		const messages = await query.take(ps.limit!).getMany();
+		const messages = await query.take(ps.limit).getMany();
 
 		// Mark all as read
 		if (ps.markAsRead) {
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
index 5ec16f5e5..60b1e2cdc 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
@@ -15,24 +13,6 @@ export const meta = {
 
 	kind: 'write:messaging',
 
-	params: {
-		userId: {
-			validator: $.optional.type(ID),
-		},
-
-		groupId: {
-			validator: $.optional.type(ID),
-		},
-
-		text: {
-			validator: $.optional.str.pipe(MessagingMessages.validateText),
-		},
-
-		fileId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -84,8 +64,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		groupId: { type: 'string', format: 'misskey:id' },
+		text: { type: 'string', nullable: true, maxLength: 3000 },
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let recipientUser: User | undefined;
 	let recipientGroup: UserGroup | undefined;
 
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
index 2975419ce..67ecd4a1e 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import ms from 'ms';
 import { ApiError } from '../../../error';
@@ -19,12 +17,6 @@ export const meta = {
 		minInterval: ms('1sec'),
 	},
 
-	params: {
-		messageId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchMessage: {
 			message: 'No such message.',
@@ -34,8 +26,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		messageId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['messageId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const message = await MessagingMessages.findOne({
 		id: ps.messageId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
index 42c3f49f6..1d0ddaf98 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { MessagingMessages } from '@/models/index';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:messaging',
 
-	params: {
-		messageId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchMessage: {
 			message: 'No such message.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		messageId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['messageId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const message = await MessagingMessages.findOne(ps.messageId);
 
 	if (message == null) {
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 16ea99af6..fdd1586ca 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import config from '@/config/index';
 import define from '../define';
 import { fetchMeta } from '@/misc/fetch-meta';
@@ -11,13 +10,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		detail: {
-			validator: $.optional.bool,
-			default: true,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -448,8 +440,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		detail: { type: 'boolean', default: true },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const instance = await fetchMeta(true);
 
 	const emojis = await Emojis.find({
diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
index 158c8877e..e61c78754 100644
--- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
+++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { AccessTokens } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -11,28 +10,6 @@ export const meta = {
 
 	secure: true,
 
-	params: {
-		session: {
-			validator: $.nullable.str,
-		},
-
-		name: {
-			validator: $.nullable.optional.str,
-		},
-
-		description: {
-			validator: $.nullable.optional.str,
-		},
-
-		iconUrl: {
-			validator: $.nullable.optional.str,
-		},
-
-		permission: {
-			validator: $.arr($.str).unique(),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -45,8 +22,22 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		session: { type: 'string', nullable: true },
+		name: { type: 'string', nullable: true },
+		description: { type: 'string', nullable: true },
+		iconUrl: { type: 'string', nullable: true },
+		permission: { type: 'array', uniqueItems: true, items: {
+			type: 'string',
+		} },
+	},
+	required: ['session', 'permission'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Generate access token
 	const accessToken = secureRndstr(32, true);
 
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 6ba5a453c..3ef97a149 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getUser } from '../../common/getters';
@@ -15,12 +13,6 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -42,8 +34,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// 自分自身
diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts
index 21948dc3d..8a88e1303 100644
--- a/packages/backend/src/server/api/endpoints/mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/mute/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getUser } from '../../common/getters';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -40,8 +32,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// Check if the mutee is yourself
diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts
index 4c6a81b63..fd8443e2b 100644
--- a/packages/backend/src/server/api/endpoints/mute/list.ts
+++ b/packages/backend/src/server/api/endpoints/mute/list.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { Mutings } from '@/models/index';
@@ -11,21 +9,6 @@ export const meta = {
 
 	kind: 'read:mutes',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 30,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -37,13 +20,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId)
 		.andWhere(`muting.muterId = :meId`, { meId: me.id });
 
 	const mutings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Mutings.packMany(mutings, me);
diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts
index 42bd5c5f7..60e501411 100644
--- a/packages/backend/src/server/api/endpoints/my/apps.ts
+++ b/packages/backend/src/server/api/endpoints/my/apps.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Apps } from '@/models/index';
 
@@ -7,18 +6,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -69,15 +56,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = {
 		userId: user.id,
 	};
 
 	const apps = await Apps.find({
 		where: query,
-		take: ps.limit!,
+		take: ps.limit,
 		skip: ps.offset,
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts
index 9edc6cb11..0106bac51 100644
--- a/packages/backend/src/server/api/endpoints/notes.ts
+++ b/packages/backend/src/server/api/endpoints/notes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../define';
 import { makePaginationQuery } from '../common/make-pagination-query';
 import { Notes } from '@/models/index';
@@ -7,41 +5,6 @@ import { Notes } from '@/models/index';
 export const meta = {
 	tags: ['notes'],
 
-	params: {
-		local: {
-			validator: $.optional.bool,
-		},
-
-		reply: {
-			validator: $.optional.bool,
-		},
-
-		renote: {
-			validator: $.optional.bool,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-		},
-
-		poll: {
-			validator: $.optional.bool,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -53,8 +16,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		local: { type: 'boolean' },
+		reply: { type: 'boolean' },
+		renote: { type: 'boolean' },
+		withFiles: { type: 'boolean' },
+		poll: { type: 'boolean' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 		.andWhere(`note.visibility = 'public'`)
 		.andWhere(`note.localOnly = FALSE`)
@@ -89,7 +67,7 @@ export default define(meta, async (ps) => {
 	//	query.isBot = bot;
 	//}
 
-	const notes = await query.take(ps.limit!).getMany();
+	const notes = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(notes);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index 088ef65e9..ea2c166b7 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { generateVisibilityQuery } from '../../common/generate-visibility-query';
@@ -14,25 +12,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -44,8 +23,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 		.andWhere(new Brackets(qb => { qb
 			.where(`note.replyId = :noteId`, { noteId: ps.noteId })
@@ -69,7 +59,7 @@ export default define(meta, async (ps, user) => {
 	if (user) generateBlockedUserQuery(query, user);
 	if (user) generateMutedInstanceQuery(query, user);
 
-	const notes = await query.take(ps.limit!).getMany();
+	const notes = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(notes, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index b89c6db4a..2caa3a499 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ClipNotes, Clips } from '@/models/index';
 import { getNote } from '../../common/getters';
@@ -11,12 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -36,8 +28,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index 4bd89c32e..c199a3f83 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getNote } from '../../common/getters';
@@ -11,22 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -46,8 +28,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
@@ -65,7 +57,7 @@ export default define(meta, async (ps, user) => {
 			conversation.push(p);
 		}
 
-		if (conversation.length == ps.limit!) {
+		if (conversation.length == ps.limit) {
 			return;
 		}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 4efa76b24..7d81abc3a 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -1,11 +1,9 @@
-import $ from 'cafy';
 import ms from 'ms';
 import { length } from 'stringz';
 import create from '@/services/note/create';
 import define from '../../define';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { ApiError } from '../../error';
-import { ID } from '@/misc/cafy-id';
 import { User } from '@/models/entities/user';
 import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index';
 import { DriveFile } from '@/models/entities/drive-file';
@@ -34,84 +32,6 @@ export const meta = {
 
 	kind: 'write:notes',
 
-	params: {
-		visibility: {
-			validator: $.optional.str.or(noteVisibilities as unknown as string[]),
-			default: 'public',
-		},
-
-		visibleUserIds: {
-			validator: $.optional.arr($.type(ID)).unique().min(0),
-		},
-
-		text: {
-			validator: $.optional.nullable.str.pipe(text =>
-				text.trim() != ''
-					&& length(text.trim()) <= maxNoteTextLength
-					&& Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH,	// DB limit
-			),
-			default: null,
-		},
-
-		cw: {
-			validator: $.optional.nullable.str.pipe(Notes.validateCw),
-		},
-
-		localOnly: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		noExtractMentions: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		noExtractHashtags: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		noExtractEmojis: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		fileIds: {
-			validator: $.optional.arr($.type(ID)).unique().range(1, 16),
-		},
-
-		mediaIds: {
-			validator: $.optional.arr($.type(ID)).unique().range(1, 16),
-			deprecated: true,
-		},
-
-		replyId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		renoteId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		channelId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		poll: {
-			validator: $.optional.nullable.obj({
-				choices: $.arr($.str)
-					.unique()
-					.range(2, 10)
-					.each(c => c.length > 0 && c.length < 50),
-				multiple: $.optional.bool,
-				expiresAt: $.optional.nullable.num.int(),
-				expiredAfter: $.optional.nullable.num.int().min(1),
-			}).strict(),
-			ref: 'poll',
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -175,8 +95,49 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" },
+		visibleUserIds: { type: 'array', uniqueItems: true, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		text: { type: 'string', nullable: true, maxLength: 3000, default: null },
+		cw: { type: 'string', nullable: true, maxLength: 100 },
+		localOnly: { type: 'boolean', default: false },
+		noExtractMentions: { type: 'boolean', default: false },
+		noExtractHashtags: { type: 'boolean', default: false },
+		noExtractEmojis: { type: 'boolean', default: false },
+		fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		replyId: { type: 'string', format: 'misskey:id', nullable: true },
+		renoteId: { type: 'string', format: 'misskey:id', nullable: true },
+		channelId: { type: 'string', format: 'misskey:id', nullable: true },
+		poll: {
+			type: 'object', nullable: true,
+			properties: {
+				choices: {
+					type: 'array', uniqueItems: true, minItems: 2, maxItems: 10, 
+					items: {
+						type: 'string', minLength: 1, maxLength: 50,
+					},
+				},
+				multiple: { type: 'boolean', default: false },
+				expiresAt: { type: 'integer', nullable: true },
+				expiredAfter: { type: 'integer', nullable: true, minimum: 1 },
+			},
+			required: ['choices'],
+		},
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let visibleUsers: User[] = [];
 	if (ps.visibleUserIds) {
 		visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id))))
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index 9e080d9e9..62a2a9846 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import deleteNote from '@/services/note/delete';
 import define from '../../define';
 import ms from 'ms';
@@ -20,12 +18,6 @@ export const meta = {
 		minInterval: ms('1sec'),
 	},
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -41,8 +33,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index 78da6a3b0..570e2354b 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getNote } from '../../../common/getters';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:favorites',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -34,8 +26,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Get favoritee
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
index 3f3d50f0d..b33a5f148 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getNote } from '../../../common/getters';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:favorites',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -33,8 +25,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Get favoritee
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index 5a47fb9e0..8a4297de6 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
 import { Notes } from '@/models/index';
@@ -9,18 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -32,8 +19,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const max = 30;
 	const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
 
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index cdd110994..23ca8752c 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { ApiError } from '../../error';
@@ -15,33 +13,6 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query';
 export const meta = {
 	tags: ['notes'],
 
-	params: {
-		withFiles: {
-			validator: $.optional.bool,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -61,8 +32,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		withFiles: { type: 'boolean' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableGlobalTimeline) {
 		if (user == null || (!user.isAdmin && !user.isModerator)) {
@@ -92,7 +76,7 @@ export default define(meta, async (ps, user) => {
 	}
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	process.nextTick(() => {
 		if (user) {
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index b43849102..ba6f3b592 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { ApiError } from '../../error';
@@ -20,48 +18,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-
-		includeMyRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeRenotedMyNotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeLocalRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -81,8 +37,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+		includeMyRenotes: { type: 'boolean', default: true },
+		includeRenotedMyNotes: { type: 'boolean', default: true },
+		includeLocalRenotes: { type: 'boolean', default: true },
+		withFiles: { type: 'boolean' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
 		throw new ApiError(meta.errors.stlDisabled);
@@ -149,7 +121,7 @@ export default define(meta, async (ps, user) => {
 	}
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	process.nextTick(() => {
 		if (user) {
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index ce0bcbeb7..7c118317d 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { ApiError } from '../../error';
@@ -17,42 +15,6 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query';
 export const meta = {
 	tags: ['notes'],
 
-	params: {
-		withFiles: {
-			validator: $.optional.bool,
-		},
-
-		fileType: {
-			validator: $.optional.arr($.str),
-		},
-
-		excludeNsfw: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -72,8 +34,25 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		withFiles: { type: 'boolean' },
+		fileType: { type: 'array', items: {
+			type: 'string',
+		} },
+		excludeNsfw: { type: 'boolean', default: false },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableLocalTimeline) {
 		if (user == null || (!user.isAdmin && !user.isModerator)) {
@@ -118,7 +97,7 @@ export default define(meta, async (ps, user) => {
 	}
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	process.nextTick(() => {
 		if (user) {
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 81b384436..265882fa7 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import read from '@/services/note/read';
 import { Notes, Followings } from '@/models/index';
@@ -15,30 +13,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		following: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		visibility: {
-			validator: $.optional.str,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -50,8 +24,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		following: { type: 'boolean', default: false },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		visibility: { type: 'string' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const followingQuery = Followings.createQueryBuilder('following')
 		.select('following.followeeId')
 		.where('following.followerId = :followerId', { followerId: user.id });
@@ -81,7 +67,7 @@ export default define(meta, async (ps, user) => {
 		query.setParameters(followingQuery.getParameters());
 	}
 
-	const mentions = await query.take(ps.limit!).getMany();
+	const mentions = await query.take(ps.limit).getMany();
 
 	read(user.id, mentions);
 
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
index 79b558e65..4030694b5 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { Polls, Mutings, Notes, PollVotes } from '@/models/index';
 import { Brackets, In } from 'typeorm';
@@ -8,18 +7,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -31,8 +18,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = Polls.createQueryBuilder('poll')
 		.where('poll.userHost IS NULL')
 		.andWhere(`poll.userId != :meId`, { meId: user.id })
@@ -64,7 +60,7 @@ export default define(meta, async (ps, user) => {
 	query.setParameters(mutingQuery.getParameters());
 	//#endregion
 
-	const polls = await query.take(ps.limit!).skip(ps.offset).getMany();
+	const polls = await query.take(ps.limit).skip(ps.offset).getMany();
 
 	if (polls.length === 0) return [];
 
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 77387cacb..739df8a03 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishNoteStream } from '@/services/stream';
 import { createNotification } from '@/services/create-notification';
 import define from '../../../define';
@@ -21,16 +19,6 @@ export const meta = {
 
 	kind: 'write:votes',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		choice: {
-			validator: $.num,
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -70,8 +58,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		choice: { type: 'integer' },
+	},
+	required: ['noteId', 'choice'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const createdAt = new Date();
 
 	// Get votee
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index 5205a7817..fdcaed64e 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { getNote } from '../../common/getters';
 import { ApiError } from '../../error';
@@ -12,34 +10,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		type: {
-			validator: $.optional.nullable.str,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num,
-			default: 0,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -59,8 +29,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		type: { type: 'string', nullable: true },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
@@ -80,7 +63,7 @@ export default define(meta, async (ps, user) => {
 
 	const reactions = await NoteReactions.find({
 		where: query,
-		take: ps.limit!,
+		take: ps.limit,
 		skip: ps.offset,
 		order: {
 			id: -1,
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index 1b42781ce..a898b3249 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import createReaction from '@/services/note/reaction/create';
 import define from '../../../define';
 import { getNote } from '../../../common/getters';
@@ -12,16 +10,6 @@ export const meta = {
 
 	kind: 'write:reactions',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		reaction: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -43,8 +31,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		reaction: { type: 'string' },
+	},
+	required: ['noteId', 'reaction'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index 1d686b597..53cadac7c 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import ms from 'ms';
 import deleteReaction from '@/services/note/reaction/delete';
@@ -19,12 +17,6 @@ export const meta = {
 		minInterval: ms('3sec'),
 	},
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -40,8 +32,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index f71d23146..aa5c2c0a4 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { getNote } from '../../common/getters';
 import { ApiError } from '../../error';
@@ -14,25 +12,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -52,8 +31,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
@@ -71,7 +61,7 @@ export default define(meta, async (ps, user) => {
 	if (user) generateMutedUserQuery(query, user);
 	if (user) generateBlockedUserQuery(query, user);
 
-	const renotes = await query.take(ps.limit!).getMany();
+	const renotes = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(renotes, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts
index 62c56534e..b88e3d716 100644
--- a/packages/backend/src/server/api/endpoints/notes/replies.ts
+++ b/packages/backend/src/server/api/endpoints/notes/replies.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Notes } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -12,25 +10,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -42,8 +21,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 		.andWhere('note.replyId = :replyId', { replyId: ps.noteId })
 		.innerJoinAndSelect('note.user', 'user')
@@ -56,7 +46,7 @@ export default define(meta, async (ps, user) => {
 	if (user) generateMutedUserQuery(query, user);
 	if (user) generateBlockedUserQuery(query, user);
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(timeline, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index 87eaffe2f..bf18a20b9 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { Notes } from '@/models/index';
@@ -13,48 +11,6 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query';
 export const meta = {
 	tags: ['notes', 'hashtags'],
 
-	params: {
-		tag: {
-			validator: $.optional.str,
-		},
-
-		query: {
-			validator: $.optional.arr($.arr($.str)),
-		},
-
-		reply: {
-			validator: $.optional.nullable.bool,
-			default: null,
-		},
-
-		renote: {
-			validator: $.optional.nullable.bool,
-			default: null,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-		},
-
-		poll: {
-			validator: $.optional.nullable.bool,
-			default: null,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -66,8 +22,28 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		tag: { type: 'string' },
+		query: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		reply: { type: 'boolean', nullable: true, default: null },
+		renote: { type: 'boolean', nullable: true, default: null },
+		withFiles: { type: 'boolean' },
+		poll: { type: 'boolean', nullable: true, default: null },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 		.innerJoinAndSelect('note.user', 'user')
 		.leftJoinAndSelect('note.reply', 'reply')
@@ -129,7 +105,7 @@ export default define(meta, async (ps, me) => {
 	}
 
 	// Search notes
-	const notes = await query.take(ps.limit!).getMany();
+	const notes = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(notes, me);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index e75212b14..bcb9c2b8a 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -1,9 +1,7 @@
-import $ from 'cafy';
 import es from '../../../../db/elasticsearch';
 import define from '../../define';
 import { Notes } from '@/models/index';
 import { In } from 'typeorm';
-import { ID } from '@/misc/cafy-id';
 import config from '@/config/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { generateVisibilityQuery } from '../../common/generate-visibility-query';
@@ -15,40 +13,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		query: {
-			validator: $.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-			default: undefined,
-		},
-
-		userId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-
-		channelId: {
-			validator: $.optional.nullable.type(ID),
-			default: null,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -63,8 +27,23 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		query: { type: 'string' },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+		host: { type: 'string', nullable: true },
+		userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+		channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
+	},
+	required: ['query'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	if (es == null) {
 		const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId);
 
@@ -86,7 +65,7 @@ export default define(meta, async (ps, me) => {
 		if (me) generateMutedUserQuery(query, me);
 		if (me) generateBlockedUserQuery(query, me);
 
-		const notes = await query.take(ps.limit!).getMany();
+		const notes = await query.take(ps.limit).getMany();
 
 		return await Notes.packMany(notes, me);
 	} else {
@@ -115,7 +94,7 @@ export default define(meta, async (ps, me) => {
 		const result = await es.search({
 			index: config.elasticsearch.index || 'misskey_note',
 			body: {
-				size: ps.limit!,
+				size: ps.limit,
 				from: ps.offset,
 				query: {
 					bool: {
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index feb94be1a..8f75eeb5e 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { getNote } from '../../common/getters';
 import { ApiError } from '../../error';
@@ -10,12 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -31,8 +23,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts
index c3e9090bb..cf5f08ea9 100644
--- a/packages/backend/src/server/api/endpoints/notes/state.ts
+++ b/packages/backend/src/server/api/endpoints/notes/state.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index';
 
@@ -8,12 +6,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -34,8 +26,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await Notes.findOneOrFail(ps.noteId);
 
 	const [favorite, watching, threadMuting] = await Promise.all([
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index a8b50d90f..4068a8a57 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { getNote } from '../../../common/getters';
 import { ApiError } from '../../../error';
@@ -14,12 +12,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -29,8 +21,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
index f76b526ce..09b64f727 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { getNote } from '../../../common/getters';
 import { ApiError } from '../../../error';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index f8cd08324..c06a98fea 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 import { Notes, Followings } from '@/models/index';
@@ -18,48 +16,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-
-		includeMyRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeRenotedMyNotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeLocalRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -71,8 +27,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+		includeMyRenotes: { type: 'boolean', default: true },
+		includeRenotedMyNotes: { type: 'boolean', default: true },
+		includeLocalRenotes: { type: 'boolean', default: true },
+		withFiles: { type: 'boolean' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const hasFollowing = (await Followings.count({
 		where: {
 			followerId: user.id,
@@ -141,7 +113,7 @@ export default define(meta, async (ps, user) => {
 	}
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	process.nextTick(() => {
 		if (user) {
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index ed069cb75..d85c8b96f 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { getNote } from '../../common/getters';
 import { ApiError } from '../../error';
@@ -15,15 +13,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-		targetLang: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -38,8 +27,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+		targetLang: { type: 'string' },
+	},
+	required: ['noteId', 'targetLang'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 8db543d32..c10d277de 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import deleteNote from '@/services/note/delete';
 import define from '../../define';
 import ms from 'ms';
@@ -20,12 +18,6 @@ export const meta = {
 		minInterval: ms('1sec'),
 	},
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -35,8 +27,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 3512fb363..018e3e06a 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { UserLists, UserListJoinings, Notes } from '@/models/index';
@@ -13,52 +11,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-
-		includeMyRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeRenotedMyNotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		includeLocalRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -78,8 +30,25 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+		includeMyRenotes: { type: 'boolean', default: true },
+		includeRenotedMyNotes: { type: 'boolean', default: true },
+		includeLocalRenotes: { type: 'boolean', default: true },
+		withFiles: { type: 'boolean' },
+	},
+	required: ['listId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const list = await UserLists.findOne({
 		id: ps.listId,
 		userId: user.id,
@@ -140,7 +109,7 @@ export default define(meta, async (ps, user) => {
 	}
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	activeUsersChart.read(user);
 
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
index 6433c6bc2..b19d37002 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import watch from '@/services/note/watch';
 import { getNote } from '../../../common/getters';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
index 3e9faa2b2..f9ceb4927 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import unwatch from '@/services/note/unwatch';
 import { getNote } from '../../../common/getters';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -27,8 +19,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts
index bd8a7ba1b..b5bfb34f4 100644
--- a/packages/backend/src/server/api/endpoints/notifications/create.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { createNotification } from '@/services/create-notification';
 
@@ -9,26 +8,22 @@ export const meta = {
 
 	kind: 'write:notifications',
 
-	params: {
-		body: {
-			validator: $.str,
-		},
-
-		header: {
-			validator: $.optional.nullable.str,
-		},
-
-		icon: {
-			validator: $.optional.nullable.str,
-		},
-	},
-
 	errors: {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		body: { type: 'string' },
+		header: { type: 'string', nullable: true },
+		icon: { type: 'string', nullable: true },
+	},
+	required: ['body'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user, token) => {
+export default define(meta, paramDef, async (ps, user, token) => {
 	createNotification(user.id, 'app', {
 		appAccessTokenId: token ? token.id : null,
 		customBody: ps.body,
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index 4cec38a95..12dc43de1 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -10,8 +10,14 @@ export const meta = {
 	kind: 'write:notifications',
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Update documents
 	await Notifications.update({
 		notifieeId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
index 7e23bc234..6e5b5616d 100644
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/read.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishMainStream } from '@/services/stream';
 import define from '../../define';
 import { Notifications } from '@/models/index';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:notifications',
 
-	params: {
-		notificationId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNotification: {
 			message: 'No such notification.',
@@ -28,8 +20,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		notificationId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['notificationId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const notification = await Notifications.findOne({
 		notifieeId: user.id,
 		id: ps.notificationId,
diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts
index 61c0160f8..20334f33f 100644
--- a/packages/backend/src/server/api/endpoints/page-push.ts
+++ b/packages/backend/src/server/api/endpoints/page-push.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../define';
-import { ID } from '@/misc/cafy-id';
 import { publishMainStream } from '@/services/stream';
 import { Users, Pages } from '@/models/index';
 import { ApiError } from '../error';
@@ -9,20 +7,6 @@ export const meta = {
 	requireCredential: true,
 	secure: true,
 
-	params: {
-		pageId: {
-			validator: $.type(ID),
-		},
-
-		event: {
-			validator: $.str,
-		},
-
-		var: {
-			validator: $.optional.nullable.any,
-		},
-	},
-
 	errors: {
 		noSuchPage: {
 			message: 'No such page.',
@@ -32,8 +16,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+		event: { type: 'string' },
+		var: {},
+	},
+	required: ['pageId', 'event'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOne(ps.pageId);
 	if (page == null) {
 		throw new ApiError(meta.errors.noSuchPage);
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 7ee50fbdf..6c00a2f77 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -1,7 +1,5 @@
-import $ from 'cafy';
 import ms from 'ms';
 import define from '../../define';
-import { ID } from '@/misc/cafy-id';
 import { Pages, DriveFiles } from '@/models/index';
 import { genId } from '@/misc/gen-id';
 import { Page } from '@/models/entities/page';
@@ -19,51 +17,6 @@ export const meta = {
 		max: 300,
 	},
 
-	params: {
-		title: {
-			validator: $.str,
-		},
-
-		name: {
-			validator: $.str.min(1),
-		},
-
-		summary: {
-			validator: $.optional.nullable.str,
-		},
-
-		content: {
-			validator: $.arr($.obj()),
-		},
-
-		variables: {
-			validator: $.arr($.obj()),
-		},
-
-		script: {
-			validator: $.str,
-		},
-
-		eyeCatchingImageId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		font: {
-			validator: $.optional.str.or(['serif', 'sans-serif']),
-			default: 'sans-serif',
-		},
-
-		alignCenter: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		hideTitleWhenPinned: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -84,8 +37,29 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		title: { type: 'string' },
+		name: { type: 'string', minLength: 1 },
+		summary: { type: 'string', nullable: true },
+		content: { type: 'array', items: {
+			type: 'object', additionalProperties: true,
+		} },
+		variables: { type: 'array', items: {
+			type: 'object', additionalProperties: true,
+		} },
+		script: { type: 'string' },
+		eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true },
+		font: { type: 'string', enum: ['serif', 'sans-serif'], default: "sans-serif" },
+		alignCenter: { type: 'boolean', default: false },
+		hideTitleWhenPinned: { type: 'boolean', default: false },
+	},
+	required: ['title', 'name', 'content', 'variables', 'script'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let eyeCatchingImage = null;
 	if (ps.eyeCatchingImageId != null) {
 		eyeCatchingImage = await DriveFiles.findOne({
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index aeda823e5..c41373bb7 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Pages } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 
 export const meta = {
 	tags: ['pages'],
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:pages',
 
-	params: {
-		pageId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPage: {
 			message: 'No such page.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['pageId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOne(ps.pageId);
 	if (page == null) {
 		throw new ApiError(meta.errors.noSuchPage);
diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts
index 7f0d58b35..38aa4dfac 100644
--- a/packages/backend/src/server/api/endpoints/pages/featured.ts
+++ b/packages/backend/src/server/api/endpoints/pages/featured.ts
@@ -17,8 +17,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Pages.createQueryBuilder('page')
 		.where('page.visibility = \'public\'')
 		.andWhere('page.likedCount > 0')
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index c479f637a..d286fd2bf 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Pages, PageLikes } from '@/models/index';
@@ -12,12 +10,6 @@ export const meta = {
 
 	kind: 'write:page-likes',
 
-	params: {
-		pageId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPage: {
 			message: 'No such page.',
@@ -39,8 +31,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['pageId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOne(ps.pageId);
 	if (page == null) {
 		throw new ApiError(meta.errors.noSuchPage);
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index 5cda5386d..95243e411 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -1,8 +1,6 @@
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Pages, Users } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 import { Page } from '@/models/entities/page';
 
 export const meta = {
@@ -10,20 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		pageId: {
-			validator: $.optional.type(ID),
-		},
-
-		name: {
-			validator: $.optional.str,
-		},
-
-		username: {
-			validator: $.optional.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -39,8 +23,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string' },
+		username: { type: 'string' },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	let page: Page | undefined;
 
 	if (ps.pageId) {
diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts
index cca5e5b5a..582c924d9 100644
--- a/packages/backend/src/server/api/endpoints/pages/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Pages, PageLikes } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:page-likes',
 
-	params: {
-		pageId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchPage: {
 			message: 'No such page.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['pageId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOne(ps.pageId);
 	if (page == null) {
 		throw new ApiError(meta.errors.noSuchPage);
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index 991085ee0..686271cf4 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -1,9 +1,7 @@
-import $ from 'cafy';
 import ms from 'ms';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Pages, DriveFiles } from '@/models/index';
-import { ID } from '@/misc/cafy-id';
 import { Not } from 'typeorm';
 
 export const meta = {
@@ -18,52 +16,6 @@ export const meta = {
 		max: 300,
 	},
 
-	params: {
-		pageId: {
-			validator: $.type(ID),
-		},
-
-		title: {
-			validator: $.str,
-		},
-
-		name: {
-			validator: $.str.min(1),
-		},
-
-		summary: {
-			validator: $.optional.nullable.str,
-		},
-
-		content: {
-			validator: $.arr($.obj()),
-		},
-
-		variables: {
-			validator: $.arr($.obj()),
-		},
-
-		script: {
-			validator: $.str,
-		},
-
-		eyeCatchingImageId: {
-			validator: $.optional.nullable.type(ID),
-		},
-
-		font: {
-			validator: $.optional.str.or(['serif', 'sans-serif']),
-		},
-
-		alignCenter: {
-			validator: $.optional.bool,
-		},
-
-		hideTitleWhenPinned: {
-			validator: $.optional.bool,
-		},
-	},
-
 	errors: {
 		noSuchPage: {
 			message: 'No such page.',
@@ -90,8 +42,30 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		pageId: { type: 'string', format: 'misskey:id' },
+		title: { type: 'string' },
+		name: { type: 'string', minLength: 1 },
+		summary: { type: 'string', nullable: true },
+		content: { type: 'array', items: {
+			type: 'object', additionalProperties: true,
+		} },
+		variables: { type: 'array', items: {
+			type: 'object', additionalProperties: true,
+		} },
+		script: { type: 'string' },
+		eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true },
+		font: { type: 'string', enum: ['serif', 'sans-serif'] },
+		alignCenter: { type: 'boolean' },
+		hideTitleWhenPinned: { type: 'boolean' },
+	},
+	required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOne(ps.pageId);
 	if (page == null) {
 		throw new ApiError(meta.errors.noSuchPage);
diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts
index 3eab70ae2..964401e19 100644
--- a/packages/backend/src/server/api/endpoints/ping.ts
+++ b/packages/backend/src/server/api/endpoints/ping.ts
@@ -5,9 +5,6 @@ export const meta = {
 
 	tags: ['meta'],
 
-	params: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -20,8 +17,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	return {
 		pong: Date.now(),
 	};
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index ff0e22555..83657f8bf 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -9,9 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -23,8 +20,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const meta = await fetchMeta();
 
 	const users = await Promise.all(meta.pinnedUsers.map(acct => Users.findOne(Acct.parse(acct))));
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index 8d8c60d75..99be33fb0 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getNote } from '../../common/getters';
@@ -11,12 +9,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		noteId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId).catch(e => {
 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 		throw e;
diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts
index af1aeb431..193c3ee79 100644
--- a/packages/backend/src/server/api/endpoints/request-reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import { publishMainStream } from '@/services/stream';
 import define from '../define';
 import rndstr from 'rndstr';
@@ -18,23 +17,22 @@ export const meta = {
 		max: 3,
 	},
 
-	params: {
-		username: {
-			validator: $.str,
-		},
-
-		email: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		username: { type: 'string' },
+		email: { type: 'string' },
+	},
+	required: ['username', 'email'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	const user = await Users.findOne({
 		usernameLower: ps.username.toLowerCase(),
 		host: IsNull(),
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index e99dc9db1..1c1cb3f13 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../define';
 import { ApiError } from '../error';
 import { resetDb } from '@/db/postgre';
@@ -6,16 +5,19 @@ import { resetDb } from '@/db/postgre';
 export const meta = {
 	requireCredential: false,
 
-	params: {
-	},
-
 	errors: {
 
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
 
 	await resetDb();
diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts
index a7366584b..04048b9ef 100644
--- a/packages/backend/src/server/api/endpoints/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/reset-password.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import { publishMainStream } from '@/services/stream';
 import define from '../define';
@@ -8,23 +7,22 @@ import { ApiError } from '../error';
 export const meta = {
 	requireCredential: false,
 
-	params: {
-		token: {
-			validator: $.str,
-		},
-
-		password: {
-			validator: $.str,
-		},
-	},
-
 	errors: {
 
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		token: { type: 'string' },
+		password: { type: 'string' },
+	},
+	required: ['token', 'password'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const req = await PasswordResetRequests.findOneOrFail({
 		token: ps.token,
 	});
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 1ad2c54ab..4a5bc6a86 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -5,17 +5,17 @@ import define from '../define';
 export const meta = {
 	requireCredential: false,
 
-	desc: {
-	},
-
 	tags: ['meta'],
+} as const;
 
-	params: {
-	},
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const memStats = await si.mem();
 	const fsStats = await si.fsSize();
 
diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts
index 82b0bb329..8abae921a 100644
--- a/packages/backend/src/server/api/endpoints/stats.ts
+++ b/packages/backend/src/server/api/endpoints/stats.ts
@@ -7,9 +7,6 @@ export const meta = {
 
 	tags: ['meta'],
 
-	params: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -46,8 +43,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async () => {
+export default define(meta, paramDef, async () => {
 	const [
 		notesCount,
 		originalNotesCount,
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index ae3e9ce77..9091c9481 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { genId } from '@/misc/gen-id';
@@ -9,20 +8,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		endpoint: {
-			validator: $.str,
-		},
-
-		auth: {
-			validator: $.str,
-		},
-
-		publickey: {
-			validator: $.str,
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -40,8 +25,18 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		endpoint: { type: 'string' },
+		auth: { type: 'string' },
+		publickey: { type: 'string' },
+	},
+	required: ['endpoint', 'auth', 'publickey'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// if already subscribed
 	const exist = await SwSubscriptions.findOne({
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts
index 6f569e941..ec994c37c 100644
--- a/packages/backend/src/server/api/endpoints/sw/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { SwSubscriptions } from '../../../../models';
 
@@ -6,16 +5,18 @@ export const meta = {
 	tags: ['account'],
 
 	requireCredential: true,
+} as const;
 
-	params: {
-		endpoint: {
-			validator: $.str,
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		endpoint: { type: 'string' },
 	},
+	required: ['endpoint'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	await SwSubscriptions.delete({
 		userId: user.id,
 		endpoint: ps.endpoint,
diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts
new file mode 100644
index 000000000..820448e67
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/test.ts
@@ -0,0 +1,22 @@
+import define from '../define';
+
+export const meta = {
+	requireCredential: false,
+} as const;
+
+const paramDef = {
+	type: 'object',
+	properties: {
+		required: { type: 'boolean' },
+		string: { type: 'string' },
+		default: { type: 'string', default: 'hello' },
+		nullableDefault: { type: 'string', nullable: true, default: 'hello' },
+		id: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['required'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, me) => {
+	return ps;
+});
diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts
index 74120fc40..6e38d4d31 100644
--- a/packages/backend/src/server/api/endpoints/username/available.ts
+++ b/packages/backend/src/server/api/endpoints/username/available.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Users, UsedUsernames } from '@/models/index';
 
@@ -7,12 +6,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		username: {
-			validator: $.use(Users.validateLocalUsername),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -25,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		username: Users.localUsernameSchema,
+	},
+	required: ['username'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps) => {
+export default define(meta, paramDef, async (ps) => {
 	// Get exist
 	const exist = await Users.count({
 		host: null,
diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts
index 6b11ec0f0..0c03ac05c 100644
--- a/packages/backend/src/server/api/endpoints/users.ts
+++ b/packages/backend/src/server/api/endpoints/users.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../define';
 import { Users } from '@/models/index';
 import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query';
@@ -9,49 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-
-		sort: {
-			validator: $.optional.str.or([
-				'+follower',
-				'-follower',
-				'+createdAt',
-				'-createdAt',
-				'+updatedAt',
-				'-updatedAt',
-			]),
-		},
-
-		state: {
-			validator: $.optional.str.or([
-				'all',
-				'admin',
-				'moderator',
-				'adminOrModerator',
-				'alive',
-			]),
-			default: 'all',
-		},
-
-		origin: {
-			validator: $.optional.str.or([
-				'combined',
-				'local',
-				'remote',
-			]),
-			default: 'local',
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -63,8 +19,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+		sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
+		state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: "all" },
+		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Users.createQueryBuilder('user');
 	query.where('user.isExplorable = TRUE');
 
@@ -93,7 +61,7 @@ export default define(meta, async (ps, me) => {
 	if (me) generateMutedUserQueryForUsers(query, me);
 	if (me) generateBlockQueryForUsers(query, me);
 
-	query.take(ps.limit!);
+	query.take(ps.limit);
 	query.skip(ps.offset);
 
 	const users = await query.getMany();
diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts
index d4152fbf5..e224bdf60 100644
--- a/packages/backend/src/server/api/endpoints/users/clips.ts
+++ b/packages/backend/src/server/api/endpoints/users/clips.ts
@@ -1,40 +1,30 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Clips } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 
 export const meta = {
 	tags: ['users', 'clips'],
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId)
 		.andWhere(`clip.userId = :userId`, { userId: ps.userId })
 		.andWhere('clip.isPublic = true');
 
 	const clips = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Clips.packMany(clips);
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 6214ab40b..5607b4cc7 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Users, Followings, UserProfiles } from '@/models/index';
@@ -11,33 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.optional.type(ID),
-		},
-
-		username: {
-			validator: $.optional.str,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -63,8 +34,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		username: { type: 'string' },
+		host: { type: 'string', nullable: true },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId != null
 		? { id: ps.userId }
 		: { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) });
@@ -98,7 +82,7 @@ export default define(meta, async (ps, me) => {
 		.innerJoinAndSelect('following.follower', 'follower');
 
 	const followings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Followings.packMany(followings, me, { populateFollower: true });
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 76112eab2..36943232d 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { Users, Followings, UserProfiles } from '@/models/index';
@@ -11,33 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.optional.type(ID),
-		},
-
-		username: {
-			validator: $.optional.str,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -63,8 +34,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		username: { type: 'string' },
+		host: { type: 'string', nullable: true },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId != null
 		? { id: ps.userId }
 		: { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) });
@@ -98,7 +82,7 @@ export default define(meta, async (ps, me) => {
 		.innerJoinAndSelect('following.followee', 'followee');
 
 	const followings = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Followings.packMany(followings, me, { populateFollowee: true });
diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
index c5f08b4c9..e874a54c7 100644
--- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
@@ -1,39 +1,29 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { GalleryPosts } from '@/models/index';
 import { makePaginationQuery } from '../../../common/make-pagination-query';
 
 export const meta = {
 	tags: ['users', 'gallery'],
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId)
 		.andWhere(`post.userId = :userId`, { userId: ps.userId });
 
 	const posts = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await GalleryPosts.packMany(posts, user);
diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
index d886d3355..a08587cd1 100644
--- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
+++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { maximum } from '@/prelude/array';
 import { ApiError } from '../../error';
@@ -12,17 +10,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -52,8 +39,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Lookup user
 	const user = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
@@ -104,7 +100,7 @@ export default define(meta, async (ps, me) => {
 	const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
 
 	// Extract top replied users
-	const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit!);
+	const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
 
 	// Make replies object (includes weights)
 	const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts
index 25e29de01..e7bc95791 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { UserGroups, UserGroupJoinings } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -12,12 +11,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		name: {
-			validator: $.str.range(1, 100),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -25,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const userGroup = await UserGroups.insert({
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
index f30ab78ca..cb30b4d81 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserGroups } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
index 7061db538..7a6a620cb 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../../define';
 import { ApiError } from '../../../../error';
 import { UserGroupJoinings, UserGroupInvitations } from '@/models/index';
@@ -13,12 +11,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		invitationId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchInvitation: {
 			message: 'No such invitation.',
@@ -28,8 +20,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		invitationId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['invitationId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the invitation
 	const invitation = await UserGroupInvitations.findOne({
 		id: ps.invitationId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
index f5ca3dec8..b5c7f0908 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../../define';
 import { ApiError } from '../../../../error';
 import { UserGroupInvitations } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		invitationId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchInvitation: {
 			message: 'No such invitation.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		invitationId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['invitationId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the invitation
 	const invitation = await UserGroupInvitations.findOne({
 		id: ps.invitationId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
index 3b7a4edb8..ba78a28a3 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
@@ -15,16 +13,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
@@ -52,8 +40,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId', 'userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
index ab48b1910..192a52a66 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
@@ -20,8 +20,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const ownedGroups = await UserGroups.find({
 		userId: me.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
index d2fcdab30..b7c7b328f 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserGroups, UserGroupJoinings } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
index 6193a7101..4592fc437 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
@@ -19,8 +19,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const userGroups = await UserGroups.find({
 		userId: me.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
index 785bea140..d50f61b15 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
@@ -12,16 +10,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
@@ -43,8 +31,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId', 'userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts
index eb26eac2a..230254060 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserGroups, UserGroupJoinings } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
index 4b1c8fbbd..4b5206fa1 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
@@ -12,16 +10,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -49,8 +37,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['groupId', 'userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts
index 6caf90355..0714fb4bd 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserGroups } from '@/models/index';
@@ -11,16 +9,6 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
-	params: {
-		groupId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.str.range(1, 100),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -36,8 +24,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		groupId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+	},
+	required: ['groupId', 'name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the group
 	const userGroup = await UserGroups.findOne({
 		id: ps.groupId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index 945b51162..0a74424e0 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../../define';
 import { UserLists } from '@/models/index';
 import { genId } from '@/misc/gen-id';
@@ -11,12 +10,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		name: {
-			validator: $.str.range(1, 100),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -24,8 +17,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+	},
+	required: ['name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const userList = await UserLists.insert({
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
index 3183d2a09..6795a227d 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserLists } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
@@ -26,8 +18,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const userList = await UserLists.findOne({
 		id: ps.listId,
 		userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts
index ae66b0aac..d43972340 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/list.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts
@@ -19,8 +19,14 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const userLists = await UserLists.find({
 		userId: me.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index 4c74aefa8..5eca5a999 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import { publishUserListStream } from '@/services/stream';
 import define from '../../../define';
 import { ApiError } from '../../../error';
@@ -13,16 +11,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
@@ -38,8 +26,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId', 'userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the list
 	const userList = await UserLists.findOne({
 		id: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index 8b50c475b..68a1f0077 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
@@ -13,16 +11,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
@@ -50,8 +38,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId', 'userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the list
 	const userList = await UserLists.findOne({
 		id: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index 06555c1a8..3e9aa836b 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserLists } from '@/models/index';
@@ -11,12 +9,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -32,8 +24,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Fetch the list
 	const userList = await UserLists.findOne({
 		id: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index 02b0d5fe1..ee1489fc6 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { UserLists } from '@/models/index';
@@ -11,16 +9,6 @@ export const meta = {
 
 	kind: 'write:account',
 
-	params: {
-		listId: {
-			validator: $.type(ID),
-		},
-
-		name: {
-			validator: $.str.range(1, 100),
-		},
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -36,8 +24,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+	},
+	required: ['listId', 'name'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the list
 	const userList = await UserLists.findOne({
 		id: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 99158fb0a..956d90b7a 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { ApiError } from '../../error';
 import { getUser } from '../../common/getters';
@@ -14,57 +12,6 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance
 export const meta = {
 	tags: ['users', 'notes'],
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		includeReplies: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-
-		includeMyRenotes: {
-			validator: $.optional.bool,
-			default: true,
-		},
-
-		withFiles: {
-			validator: $.optional.bool,
-			default: false,
-		},
-
-		fileType: {
-			validator: $.optional.arr($.str),
-		},
-
-		excludeNsfw: {
-			validator: $.optional.bool,
-			default: false,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -84,8 +31,28 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		includeReplies: { type: 'boolean', default: true },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+		includeMyRenotes: { type: 'boolean', default: true },
+		withFiles: { type: 'boolean', default: false },
+		fileType: { type: 'array', items: {
+			type: 'string',
+		} },
+		excludeNsfw: { type: 'boolean', default: false },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Lookup user
 	const user = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
@@ -141,7 +108,7 @@ export default define(meta, async (ps, me) => {
 
 	//#endregion
 
-	const timeline = await query.take(ps.limit!).getMany();
+	const timeline = await query.take(ps.limit).getMany();
 
 	return await Notes.packMany(timeline, me);
 });
diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts
index 6e003dd1a..873b5e80f 100644
--- a/packages/backend/src/server/api/endpoints/users/pages.ts
+++ b/packages/backend/src/server/api/endpoints/users/pages.ts
@@ -1,40 +1,30 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { Pages } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
 
 export const meta = {
 	tags: ['users', 'pages'],
+} as const;
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
 	},
+	required: ['userId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, user) => {
+export default define(meta, paramDef, async (ps, user) => {
 	const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId)
 		.andWhere(`page.userId = :userId`, { userId: ps.userId })
 		.andWhere('page.visibility = \'public\'');
 
 	const pages = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Pages.packMany(pages);
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 312d4dbf2..8d968210b 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -1,5 +1,3 @@
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { NoteReactions, UserProfiles } from '@/models/index';
 import { makePaginationQuery } from '../../common/make-pagination-query';
@@ -11,33 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		sinceId: {
-			validator: $.optional.type(ID),
-		},
-
-		untilId: {
-			validator: $.optional.type(ID),
-		},
-
-		sinceDate: {
-			validator: $.optional.num,
-		},
-
-		untilDate: {
-			validator: $.optional.num,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -57,8 +28,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const profile = await UserProfiles.findOneOrFail(ps.userId);
 
 	if (me == null || (me.id !== ps.userId && !profile.publicReactions)) {
@@ -73,7 +57,7 @@ export default define(meta, async (ps, me) => {
 	generateVisibilityQuery(query, me);
 
 	const reactions = await query
-		.take(ps.limit!)
+		.take(ps.limit)
 		.getMany();
 
 	return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index 9ea39eb2d..4a2d8d7e4 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -1,5 +1,4 @@
 import ms from 'ms';
-import $ from 'cafy';
 import define from '../../define';
 import { Users, Followings } from '@/models/index';
 import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query';
@@ -12,18 +11,6 @@ export const meta = {
 
 	kind: 'read:account',
 
-	params: {
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -35,8 +22,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		offset: { type: 'integer', default: 0 },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const query = Users.createQueryBuilder('user')
 		.where('user.isLocked = FALSE')
 		.andWhere('user.isExplorable = TRUE')
@@ -58,7 +54,7 @@ export default define(meta, async (ps, me) => {
 
 	query.setParameters(followingQuery.getParameters());
 
-	const users = await query.take(ps.limit!).skip(ps.offset).getMany();
+	const users = await query.take(ps.limit).skip(ps.offset).getMany();
 
 	return await Users.packMany(users, me, { detail: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index 7e319ca10..fac1a4b92 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import define from '../../define';
-import { ID } from '@/misc/cafy-id';
 import { Users } from '@/models/index';
 
 export const meta = {
@@ -8,12 +6,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		userId: {
-			validator: $.either($.type(ID), $.arr($.type(ID)).unique()),
-		},
-	},
-
 	res: {
 		optional: false, nullable: false,
 		oneOf: [
@@ -101,8 +93,24 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: {
+			anyOf: [
+				{ type: 'string', format: 'misskey:id' },
+				{
+					type: 'array',
+					items: { type: 'string', format: 'misskey:id' },
+				},
+			],
+		},
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId];
 
 	const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id)));
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index ed2aa7bb2..1df5e1573 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -1,6 +1,4 @@
-import $ from 'cafy';
 import * as sanitizeHtml from 'sanitize-html';
-import { ID } from '@/misc/cafy-id';
 import define from '../../define';
 import { publishAdminStream } from '@/services/stream';
 import { ApiError } from '../../error';
@@ -15,16 +13,6 @@ export const meta = {
 
 	requireCredential: true,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-
-		comment: {
-			validator: $.str.range(1, 2048),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -46,8 +34,17 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		comment: { type: 'string', minLength: 1, maxLength: 2048 },
+	},
+	required: ['userId', 'comment'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	// Lookup user
 	const user = await getUser(ps.userId).catch(e => {
 		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 72e79c882..433779ae2 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { Followings, Users } from '@/models/index';
 import { Brackets } from 'typeorm';
@@ -10,26 +9,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		username: {
-			validator: $.optional.nullable.str,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		detail: {
-			validator: $.optional.bool,
-			default: true,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -41,8 +20,19 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		username: { type: 'string', nullable: true },
+		host: { type: 'string', nullable: true },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		detail: { type: 'boolean', default: true },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
 
 	if (ps.host) {
@@ -57,7 +47,7 @@ export default define(meta, async (ps, me) => {
 		q.andWhere('user.updatedAt IS NOT NULL');
 		q.orderBy('user.updatedAt', 'DESC');
 
-		const users = await q.take(ps.limit!).getMany();
+		const users = await q.take(ps.limit).getMany();
 
 		return await Users.packMany(users, me, { detail: ps.detail });
 	} else if (ps.username) {
@@ -82,10 +72,10 @@ export default define(meta, async (ps, me) => {
 
 			users = await query
 				.orderBy('user.usernameLower', 'ASC')
-				.take(ps.limit!)
+				.take(ps.limit)
 				.getMany();
 
-			if (users.length < ps.limit!) {
+			if (users.length < ps.limit) {
 				const otherQuery = await Users.createQueryBuilder('user')
 					.where(`user.id NOT IN (${ followingQuery.getQuery() })`)
 					.andWhere(`user.id != :meId`, { meId: me.id })
@@ -97,7 +87,7 @@ export default define(meta, async (ps, me) => {
 
 				const otherUsers = await otherQuery
 					.orderBy('user.updatedAt', 'DESC')
-					.take(ps.limit! - users.length)
+					.take(ps.limit - users.length)
 					.getMany();
 
 				users = users.concat(otherUsers);
@@ -108,7 +98,7 @@ export default define(meta, async (ps, me) => {
 				.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
 				.andWhere('user.updatedAt IS NOT NULL')
 				.orderBy('user.updatedAt', 'DESC')
-				.take(ps.limit! - users.length)
+				.take(ps.limit - users.length)
 				.getMany();
 		}
 
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index 26f818afc..8c28e7c3a 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -1,4 +1,3 @@
-import $ from 'cafy';
 import define from '../../define';
 import { UserProfiles, Users } from '@/models/index';
 import { User } from '@/models/entities/user';
@@ -9,32 +8,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		query: {
-			validator: $.str,
-		},
-
-		offset: {
-			validator: $.optional.num.min(0),
-			default: 0,
-		},
-
-		limit: {
-			validator: $.optional.num.range(1, 100),
-			default: 10,
-		},
-
-		origin: {
-			validator: $.optional.str.or(['local', 'remote', 'combined']),
-			default: 'combined',
-		},
-
-		detail: {
-			validator: $.optional.bool,
-			default: true,
-		},
-	},
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -46,8 +19,20 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		query: { type: 'string' },
+		offset: { type: 'integer', default: 0 },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		origin: { type: 'string', enum: ['local', 'remote', 'combined'], default: "combined" },
+		detail: { type: 'boolean', default: true },
+	},
+	required: ['query'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
 
 	const isUsername = ps.query.startsWith('@');
@@ -71,7 +56,7 @@ export default define(meta, async (ps, me) => {
 
 		users = await usernameQuery
 			.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
-			.take(ps.limit!)
+			.take(ps.limit)
 			.skip(ps.offset)
 			.getMany();
 	} else {
@@ -91,11 +76,11 @@ export default define(meta, async (ps, me) => {
 
 		users = await nameQuery
 			.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
-			.take(ps.limit!)
+			.take(ps.limit)
 			.skip(ps.offset)
 			.getMany();
 
-		if (users.length < ps.limit!) {
+		if (users.length < ps.limit) {
 			const profQuery = UserProfiles.createQueryBuilder('prof')
 				.select('prof.userId')
 				.where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
@@ -117,7 +102,7 @@ export default define(meta, async (ps, me) => {
 
 			users = users.concat(await query
 				.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
-				.take(ps.limit!)
+				.take(ps.limit)
 				.skip(ps.offset)
 				.getMany()
 			);
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 92910e9ed..29c90963a 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -1,9 +1,7 @@
-import $ from 'cafy';
 import { resolveUser } from '@/remote/resolve-user';
 import define from '../../define';
 import { apiLogger } from '../../logger';
 import { ApiError } from '../../error';
-import { ID } from '@/misc/cafy-id';
 import { Users } from '@/models/index';
 import { In } from 'typeorm';
 import { User } from '@/models/entities/user';
@@ -13,24 +11,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.optional.type(ID),
-		},
-
-		userIds: {
-			validator: $.optional.arr($.type(ID)).unique(),
-		},
-
-		username: {
-			validator: $.optional.str,
-		},
-
-		host: {
-			validator: $.optional.nullable.str,
-		},
-	},
-
 	res: {
 		optional: false, nullable: false,
 		oneOf: [
@@ -64,8 +44,21 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		userIds: { type: 'array', uniqueItems: true, items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		username: { type: 'string' },
+		host: { type: 'string', nullable: true },
+	},
+	required: [],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	let user;
 
 	const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts
index 381e43347..29dd8fdf9 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -1,7 +1,5 @@
-import $ from 'cafy';
 import define from '../../define';
 import { ApiError } from '../../error';
-import { ID } from '@/misc/cafy-id';
 import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index';
 
 export const meta = {
@@ -9,12 +7,6 @@ export const meta = {
 
 	requireCredential: false,
 
-	params: {
-		userId: {
-			validator: $.type(ID),
-		},
-	},
-
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -24,8 +16,16 @@ export const meta = {
 	},
 } as const;
 
+const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
 // eslint-disable-next-line import/no-default-export
-export default define(meta, async (ps, me) => {
+export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOne(ps.userId);
 	if (user == null) {
 		throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 1efef8d26..fe5a4715e 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -1,5 +1,4 @@
 import endpoints from '../endpoints';
-import { Context } from 'cafy';
 import config from '@/config/index';
 import { errors as basicErrors } from './errors';
 import { schemas, convertSchemaToOpenApiSchema } from './schemas';
@@ -38,47 +37,7 @@ export function genOpenapiSpec(lang = 'ja-JP') {
 		},
 	};
 
-	function genProps(props: { [key: string]: Context; }) {
-		const properties = {} as any;
-
-		for (const [k, v] of Object.entries(props)) {
-			properties[k] = genProp(v);
-		}
-
-		return properties;
-	}
-
-	function genProp(param: Context): any {
-		const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
-		return {
-			description: (param.data || {}).desc,
-			default: (param.data || {}).default,
-			deprecated: (param.data || {}).deprecated,
-			...((param.data || {}).default ? { default: (param.data || {}).default } : {}),
-			type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
-			...(param.name === 'ID' ? { example: 'xxxxxxxxxx', format: 'id' } : {}),
-			nullable: param.isNullable,
-			...(param.name === 'String' ? {
-				...((param as any).enum ? { enum: (param as any).enum } : {}),
-				...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
-				...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
-			} : {}),
-			...(param.name === 'Number' ? {
-				...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
-				...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
-			} : {}),
-			...(param.name === 'Object' ? {
-				...(required.length > 0 ? { required } : {}),
-				properties: (param as any).props ? genProps((param as any).props) : {},
-			} : {}),
-			...(param.name === 'Array' ? {
-				items: (param as any).ctx ? genProp((param as any).ctx) : {},
-			} : {}),
-		};
-	}
-
 	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
-		const porops = {} as any;
 		const errors = {} as any;
 
 		if (endpoint.meta.errors) {
@@ -91,21 +50,9 @@ export function genOpenapiSpec(lang = 'ja-JP') {
 			}
 		}
 
-		if (endpoint.meta.params) {
-			for (const [k, v] of Object.entries(endpoint.meta.params)) {
-				if (v.validator.data == null) v.validator.data = {};
-				if (v.desc) v.validator.data.desc = v.desc[lang];
-				if (v.deprecated) v.validator.data.deprecated = v.deprecated;
-				if (v.default) v.validator.data.default = v.default;
-				porops[k] = v.validator;
-			}
-		}
-
-		const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
-
 		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
 
-		let desc = (endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.') + '\n\n';
+		let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
 		desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
 		if (endpoint.meta.kind) {
 			const kind = endpoint.meta.kind;
@@ -132,11 +79,7 @@ export function genOpenapiSpec(lang = 'ja-JP') {
 				required: true,
 				content: {
 					'application/json': {
-						schema: {
-							type: 'object',
-							...(required.length > 0 ? { required } : {}),
-							properties: endpoint.meta.params ? genProps(porops) : {},
-						},
+						schema: endpoint.params,
 					},
 				},
 			},
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index eb42667fd..5d062168e 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -1,6 +1,6 @@
-import { refs, MinimumSchema } from '@/misc/schema';
+import { refs, Schema } from '@/misc/schema';
 
-export function convertSchemaToOpenApiSchema(schema: MinimumSchema) {
+export function convertSchemaToOpenApiSchema(schema: Schema) {
 	const res: any = schema;
 
 	if (schema.type === 'object' && schema.properties) {
diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts
index eaab8e711..d42c270bb 100644
--- a/packages/backend/src/server/api/private/signup.ts
+++ b/packages/backend/src/server/api/private/signup.ts
@@ -38,7 +38,7 @@ export default async (ctx: Koa.Context) => {
 	const emailAddress = body['emailAddress'];
 
 	if (instance.emailRequiredForSignup) {
-		if (emailAddress == null || typeof emailAddress != 'string') {
+		if (emailAddress == null || typeof emailAddress !== 'string') {
 			ctx.status = 400;
 			return;
 		}
@@ -51,7 +51,7 @@ export default async (ctx: Koa.Context) => {
 	}
 
 	if (instance.disableRegistration) {
-		if (invitationCode == null || typeof invitationCode != 'string') {
+		if (invitationCode == null || typeof invitationCode !== 'string') {
 			ctx.status = 400;
 			return;
 		}
diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts
index cc10900e9..c6bea0fee 100644
--- a/packages/backend/src/services/chart/core.ts
+++ b/packages/backend/src/services/chart/core.ts
@@ -82,6 +82,38 @@ type Unflatten<T extends Record<string, any>> = UnionToIntersection<
 	}[Extract<keyof T, string>]
 >;
 
+type ToJsonSchema<S> = {
+	type: 'object';
+	properties: {
+		[K in keyof S]: S[K] extends number[] ? { type: 'array'; items: { type: 'number'; }; } : ToJsonSchema<S[K]>;
+	},
+	required: (keyof S)[];
+};
+
+export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> {
+	const object = {};
+	for (const [k, v] of Object.entries(schema)) {
+		nestedProperty.set(object, k, null);
+	}
+
+	function f(obj: Record<string, null | Record<string, unknown>>) {
+		const jsonSchema = {
+			type: 'object',
+			properties: {} as Record<string, unknown>,
+			required: [],
+		};
+		for (const [k, v] of Object.entries(obj)) {
+			jsonSchema.properties[k] = v === null ? {
+				type: 'array',
+				items: { type: 'number' },
+			} : f(v as Record<string, null | Record<string, unknown>>);
+		}
+		return jsonSchema;
+	}
+
+	return f(object) as ToJsonSchema<Unflatten<ChartResult<S>>>;
+}
+
 /**
  * 様々なチャートの管理を司るクラス
  */
diff --git a/packages/backend/test/api.ts b/packages/backend/test/api.ts
index 99fb196dc..c4802e224 100644
--- a/packages/backend/test/api.ts
+++ b/packages/backend/test/api.ts
@@ -1,970 +1,83 @@
-/*
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as childProcess from 'child_process';
-import { async, signup, request, post, react, uploadFile } from './utils';
+import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils';
 
 describe('API', () => {
 	let p: childProcess.ChildProcess;
+	let alice: any;
+	let bob: any;
+	let carol: any;
 
-	beforeEach(done => {
-		p = childProcess.spawn('node', [__dirname + '/../index.js'], {
-			stdio: ['inherit', 'inherit', 'ipc'],
-			env: { NODE_ENV: 'test' }
-		});
-		p.on('message', message => {
-			if (message === 'ok') {
-				done();
-			}
-		});
+	before(async () => {
+		p = await startServer();
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+		carol = await signup({ username: 'carol' });
 	});
 
-	afterEach(() => {
-		p.kill();
+	after(async () => {
+		await shutdownServer(p);
 	});
 
-	describe('signup', () => {
-		it('不正なユーザー名でアカウントが作成できない', async(async () => {
-			const res = await request('/signup', {
-				username: 'test.',
-				password: 'test'
+	describe('General validation', () => {
+		it('wrong type', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				string: 42,
 			});
 			assert.strictEqual(res.status, 400);
 		}));
 
-		it('空のパスワードでアカウントが作成できない', async(async () => {
-			const res = await request('/signup', {
-				username: 'test',
-				password: ''
+		it('missing require param', async(async () => {
+			const res = await request('/test', {
+				string: 'a',
 			});
 			assert.strictEqual(res.status, 400);
 		}));
 
-		it('正しくアカウントが作成できる', async(async () => {
-			const me = {
-				username: 'test',
-				password: 'test'
-			};
-
-			const res = await request('/signup', me);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.username, me.username);
-		}));
-
-		it('同じユーザー名のアカウントは作成できない', async(async () => {
-			await signup({
-				username: 'test'
-			});
-
-			const res = await request('/signup', {
-				username: 'test',
-				password: 'test'
-			});
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('signin', () => {
-		it('間違ったパスワードでサインインできない', async(async () => {
-			await signup({
-				username: 'test',
-				password: 'foo'
-			});
-
-			const res = await request('/signin', {
-				username: 'test',
-				password: 'bar'
-			});
-
-			assert.strictEqual(res.status, 403);
-		}));
-
-		it('クエリをインジェクションできない', async(async () => {
-			await signup({
-				username: 'test'
-			});
-
-			const res = await request('/signin', {
-				username: 'test',
-				password: {
-					$gt: ''
-				}
-			});
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('正しい情報でサインインできる', async(async () => {
-			await signup({
-				username: 'test',
-				password: 'foo'
-			});
-
-			const res = await request('/signin', {
-				username: 'test',
-				password: 'foo'
-			});
-
-			assert.strictEqual(res.status, 200);
-		}));
-	});
-
-	describe('i/update', () => {
-		it('アカウント設定を更新できる', async(async () => {
-			const me = await signup();
-
-			const myName = '大室櫻子';
-			const myLocation = '七森中';
-			const myBirthday = '2000-09-07';
-
-			const res = await request('/i/update', {
-				name: myName,
-				location: myLocation,
-				birthday: myBirthday
-			}, me);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, myName);
-			assert.strictEqual(res.body.location, myLocation);
-			assert.strictEqual(res.body.birthday, myBirthday);
-		}));
-
-		it('名前を空白にできない', async(async () => {
-			const me = await signup();
-			const res = await request('/i/update', {
-				name: ' '
-			}, me);
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('誕生日の設定を削除できる', async(async () => {
-			const me = await signup();
-			await request('/i/update', {
-				birthday: '2000-09-07'
-			}, me);
-
-			const res = await request('/i/update', {
-				birthday: null
-			}, me);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.birthday, null);
-		}));
-
-		it('不正な誕生日の形式で怒られる', async(async () => {
-			const me = await signup();
-			const res = await request('/i/update', {
-				birthday: '2000/09/07'
-			}, me);
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('users/show', () => {
-		it('ユーザーが取得できる', async(async () => {
-			const me = await signup();
-
-			const res = await request('/users/show', {
-				userId: me.id
-			}, me);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.id, me.id);
-		}));
-
-		it('ユーザーが存在しなかったら怒る', async(async () => {
-			const res = await request('/users/show', {
-				userId: '000000000000000000000000'
+		it('invalid misskey:id (empty string)', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				id: '',
 			});
 			assert.strictEqual(res.status, 400);
 		}));
 
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/users/show', {
-				userId: 'kyoppie'
+		it('valid misskey:id', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				id: '8wvhjghbxu',
 			});
-			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.status, 200);
 		}));
-	});
 
-	describe('notes/show', () => {
-		it('投稿が取得できる', async(async () => {
-			const me = await signup();
-			const myPost = await post(me, {
-				text: 'test'
+		it('default value', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				string: 'a',
 			});
-
-			const res = await request('/notes/show', {
-				noteId: myPost.id
-			}, me);
-
 			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.id, myPost.id);
-			assert.strictEqual(res.body.text, myPost.text);
+			assert.strictEqual(res.body.default, 'hello');
 		}));
 
-		it('投稿が存在しなかったら怒る', async(async () => {
-			const res = await request('/notes/show', {
-				noteId: '000000000000000000000000'
+		it('can set null even if it has default value', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				nullableDefault: null,
 			});
-			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(res.body.nullableDefault, null);
 		}));
 
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/notes/show', {
-				noteId: 'kyoppie'
+		it('cannot set undefined if it has default value', async(async () => {
+			const res = await request('/test', {
+				required: true,
+				nullableDefault: undefined,
 			});
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('notes/reactions/create', () => {
-		it('リアクションできる', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const bobPost = await post(bob);
-
-			const alice = await signup({ username: 'alice' });
-			const res = await request('/notes/reactions/create', {
-				noteId: bobPost.id,
-				reaction: 'like'
-			}, alice);
-
-			assert.strictEqual(res.status, 204);
-		}));
-
-		it('自分の投稿にはリアクションできない', async(async () => {
-			const me = await signup();
-			const myPost = await post(me);
-
-			const res = await request('/notes/reactions/create', {
-				noteId: myPost.id,
-				reaction: 'like'
-			}, me);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('二重にリアクションできない', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const bobPost = await post(bob);
-
-			const alice = await signup({ username: 'alice' });
-			await react(alice, bobPost, 'like');
-
-			const res = await request('/notes/reactions/create', {
-				noteId: bobPost.id,
-				reaction: 'like'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しない投稿にはリアクションできない', async(async () => {
-			const me = await signup();
-
-			const res = await request('/notes/reactions/create', {
-				noteId: '000000000000000000000000',
-				reaction: 'like'
-			}, me);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const me = await signup();
-
-			const res = await request('/notes/reactions/create', {}, me);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const me = await signup();
-
-			const res = await request('/notes/reactions/create', {
-				noteId: 'kyoppie',
-				reaction: 'like'
-			}, me);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('following/create', () => {
-		it('フォローできる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
 			assert.strictEqual(res.status, 200);
-		}));
-
-		it('既にフォローしている場合は怒る', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-			await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーはフォローできない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/create', {
-				userId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('自分自身はフォローできない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/create', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/create', {
-				userId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('following/delete', () => {
-		it('フォロー解除できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-			await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 200);
-		}));
-
-		it('フォローしていない場合は怒る', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーはフォロー解除できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/delete', {
-				userId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('自分自身はフォロー解除できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/delete', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/following/delete', {
-				userId: 'kyoppie'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('drive', () => {
-		it('ドライブ情報を取得できる', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			await uploadFile({
-				userId: me.id,
-				size: 256
-			});
-			await uploadFile({
-				userId: me.id,
-				size: 512
-			});
-			await uploadFile({
-				userId: me.id,
-				size: 1024
-			});
-			const res = await request('/drive', {}, me);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			expect(res.body).have.property('usage').eql(1792);
-		}));
-	});
-
-	describe('drive/files/create', () => {
-		it('ファイルを作成できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await uploadFile(alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'Lenna.png');
-		}));
-
-		it('ファイルに名前を付けられる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await assert.request(server)
-				.post('/drive/files/create')
-				.field('i', alice.token)
-				.field('name', 'Belmond.png')
-				.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
-
-			expect(res).have.status(200);
-			expect(res.body).be.a('object');
-			expect(res.body).have.property('name').eql('Belmond.png');
-		}));
-
-		it('ファイル無しで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/files/create', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('SVGファイルを作成できる', async(async () => {
-			const izumi = await signup({ username: 'izumi' });
-
-			const res = await uploadFile(izumi, __dirname + '/resources/image.svg');
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'image.svg');
-			assert.strictEqual(res.body.type, 'image/svg+xml');
-		}));
-	});
-
-	describe('drive/files/update', () => {
-		it('名前を更新できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-			const newName = 'いちごパスタ.png';
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				name: newName
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, newName);
-		}));
-
-		it('他人のファイルは更新できない', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(bob);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('親フォルダを更新できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.folderId, folder.id);
-		}));
-
-		it('親フォルダを無しにできる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: null
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.folderId, null);
-		}));
-
-		it('他人のフォルダには入れられない', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, bob)).body;
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないフォルダで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なフォルダIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const file = await uploadFile(alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('ファイルが存在しなかったら怒る', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/files/update', {
-				fileId: '000000000000000000000000',
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/files/update', {
-				fileId: 'kyoppie',
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('drive/folders/create', () => {
-		it('フォルダを作成できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/folders/create', {
-				name: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'test');
-		}));
-	});
-
-	describe('drive/folders/update', () => {
-		it('名前を更新できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				name: 'new name'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'new name');
-		}));
-
-		it('他人のフォルダを更新できない', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, bob)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				name: 'new name'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('親フォルダを更新できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.parentId, parentFolder.id);
-		}));
-
-		it('親フォルダを無しに更新できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: null
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.parentId, null);
-		}));
-
-		it('他人のフォルダを親フォルダに設定できない', async(async () => {
-			const bob = await signup({ username: 'bob' });
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, bob)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: parentFolder.id,
-				parentId: folder.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない(再帰的)', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folderA = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const folderB = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const folderC = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: folderB.id,
-				parentId: folderA.id
-			}, alice);
-			await request('/drive/folders/update', {
-				folderId: folderC.id,
-				parentId: folderB.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folderA.id,
-				parentId: folderC.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない(自身)', async(async () => {
-			const arisugawa = await signup({ username: 'arisugawa' });
-			const folderA = (await request('/drive/folders/create', {
-				name: 'test'
-			}, arisugawa)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folderA.id,
-				parentId: folderA.id
-			}, arisugawa);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しない親フォルダを設定できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正な親フォルダIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないフォルダを更新できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/folders/update', {
-				folderId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なフォルダIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/drive/folders/update', {
-				folderId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('messaging/messages/create', () => {
-		it('メッセージを送信できる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id,
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.text, 'test');
-		}));
-
-		it('自分自身にはメッセージを送信できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: alice.id,
-				text: 'Yo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーにはメッセージを送信できない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: '000000000000000000000000',
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なユーザーIDで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: 'foo',
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('テキストが無くて怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('文字数オーバーで怒られる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id,
-				text: '!'.repeat(1001)
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('notes/replies', () => {
-		it('自分に閲覧権限のない投稿は含まれない', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-			const carol = await signup({ username: 'carol' });
-
-			const alicePost = await post(alice, {
-				text: 'foo'
-			});
-
-			await post(bob, {
-				replyId: alicePost.id,
-				text: 'bar',
-				visibility: 'specified',
-				visibleUserIds: [alice.id]
-			});
-
-			const res = await request('/notes/replies', {
-				noteId: alicePost.id
-			}, carol);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(res.body.length, 0);
-		}));
-	});
-
-	describe('notes/timeline', () => {
-		it('フォロワー限定投稿が含まれる', async(async () => {
-			const alice = await signup({ username: 'alice' });
-			const bob = await signup({ username: 'bob' });
-
-			await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			const alicePost = await post(alice, {
-				text: 'foo',
-				visibility: 'followers'
-			});
-
-			const res = await request('/notes/timeline', {}, bob);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(res.body.length, 1);
-			assert.strictEqual(res.body[0].id, alicePost.id);
+			assert.strictEqual(res.body.nullableDefault, 'hello');
 		}));
 	});
 });
-*/
diff --git a/packages/backend/test/endpoints.ts b/packages/backend/test/endpoints.ts
new file mode 100644
index 000000000..4df080030
--- /dev/null
+++ b/packages/backend/test/endpoints.ts
@@ -0,0 +1,858 @@
+/*
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import * as childProcess from 'child_process';
+import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils';
+
+describe('API: Endpoints', () => {
+	let p: childProcess.ChildProcess;
+	let alice: any;
+	let bob: any;
+	let carol: any;
+
+	before(async () => {
+		p = await startServer();
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+		carol = await signup({ username: 'carol' });
+	});
+
+	after(async () => {
+		await shutdownServer(p);
+	});
+
+	describe('signup', () => {
+		it('不正なユーザー名でアカウントが作成できない', async(async () => {
+			const res = await request('/signup', {
+				username: 'test.',
+				password: 'test'
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('空のパスワードでアカウントが作成できない', async(async () => {
+			const res = await request('/signup', {
+				username: 'test',
+				password: ''
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('正しくアカウントが作成できる', async(async () => {
+			const me = {
+				username: 'test1',
+				password: 'test1'
+			};
+
+			const res = await request('/signup', me);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.username, me.username);
+		}));
+
+		it('同じユーザー名のアカウントは作成できない', async(async () => {
+			await signup({
+				username: 'test2'
+			});
+
+			const res = await request('/signup', {
+				username: 'test2',
+				password: 'test2'
+			});
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('signin', () => {
+		it('間違ったパスワードでサインインできない', async(async () => {
+			await signup({
+				username: 'test3',
+				password: 'foo'
+			});
+
+			const res = await request('/signin', {
+				username: 'test3',
+				password: 'bar'
+			});
+
+			assert.strictEqual(res.status, 403);
+		}));
+
+		it('クエリをインジェクションできない', async(async () => {
+			await signup({
+				username: 'test4'
+			});
+
+			const res = await request('/signin', {
+				username: 'test4',
+				password: {
+					$gt: ''
+				}
+			});
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('正しい情報でサインインできる', async(async () => {
+			await signup({
+				username: 'test5',
+				password: 'foo'
+			});
+
+			const res = await request('/signin', {
+				username: 'test5',
+				password: 'foo'
+			});
+
+			assert.strictEqual(res.status, 200);
+		}));
+	});
+
+	describe('i/update', () => {
+		it('アカウント設定を更新できる', async(async () => {
+			const myName = '大室櫻子';
+			const myLocation = '七森中';
+			const myBirthday = '2000-09-07';
+
+			const res = await request('/i/update', {
+				name: myName,
+				location: myLocation,
+				birthday: myBirthday
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, myName);
+			assert.strictEqual(res.body.location, myLocation);
+			assert.strictEqual(res.body.birthday, myBirthday);
+		}));
+
+		it('名前を空白にできない', async(async () => {
+			const res = await request('/i/update', {
+				name: ' '
+			}, alice);
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('誕生日の設定を削除できる', async(async () => {
+			await request('/i/update', {
+				birthday: '2000-09-07'
+			}, alice);
+
+			const res = await request('/i/update', {
+				birthday: null
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.birthday, null);
+		}));
+
+		it('不正な誕生日の形式で怒られる', async(async () => {
+			const res = await request('/i/update', {
+				birthday: '2000/09/07'
+			}, alice);
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('users/show', () => {
+		it('ユーザーが取得できる', async(async () => {
+			const res = await request('/users/show', {
+				userId: alice.id
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.id, alice.id);
+		}));
+
+		it('ユーザーが存在しなかったら怒る', async(async () => {
+			const res = await request('/users/show', {
+				userId: '000000000000000000000000'
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/users/show', {
+				userId: 'kyoppie'
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('notes/show', () => {
+		it('投稿が取得できる', async(async () => {
+			const myPost = await post(alice, {
+				text: 'test'
+			});
+
+			const res = await request('/notes/show', {
+				noteId: myPost.id
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.id, myPost.id);
+			assert.strictEqual(res.body.text, myPost.text);
+		}));
+
+		it('投稿が存在しなかったら怒る', async(async () => {
+			const res = await request('/notes/show', {
+				noteId: '000000000000000000000000'
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/notes/show', {
+				noteId: 'kyoppie'
+			});
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('notes/reactions/create', () => {
+		it('リアクションできる', async(async () => {
+			const bobPost = await post(bob);
+
+			const alice = await signup({ username: 'alice' });
+			const res = await request('/notes/reactions/create', {
+				noteId: bobPost.id,
+				reaction: '👍',
+			}, alice);
+
+			assert.strictEqual(res.status, 204);
+		}));
+
+		it('自分の投稿にもリアクションできる', async(async () => {
+			const myPost = await post(alice);
+
+			const res = await request('/notes/reactions/create', {
+				noteId: myPost.id,
+				reaction: '👍',
+			}, alice);
+
+			assert.strictEqual(res.status, 204);
+		}));
+
+		it('二重にリアクションできない', async(async () => {
+			const bobPost = await post(bob);
+
+			await react(alice, bobPost, 'like');
+
+			const res = await request('/notes/reactions/create', {
+				noteId: bobPost.id,
+				reaction: '👍',
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しない投稿にはリアクションできない', async(async () => {
+			const res = await request('/notes/reactions/create', {
+				noteId: '000000000000000000000000',
+				reaction: '👍',
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('空のパラメータで怒られる', async(async () => {
+			const res = await request('/notes/reactions/create', {}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/notes/reactions/create', {
+				noteId: 'kyoppie',
+				reaction: '👍',
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('following/create', () => {
+		it('フォローできる', async(async () => {
+			const res = await request('/following/create', {
+				userId: alice.id
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+		}));
+
+		it('既にフォローしている場合は怒る', async(async () => {
+			const res = await request('/following/create', {
+				userId: alice.id
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しないユーザーはフォローできない', async(async () => {
+			const res = await request('/following/create', {
+				userId: '000000000000000000000000'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('自分自身はフォローできない', async(async () => {
+			const res = await request('/following/create', {
+				userId: alice.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('空のパラメータで怒られる', async(async () => {
+			const res = await request('/following/create', {}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/following/create', {
+				userId: 'foo'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('following/delete', () => {
+		it('フォロー解除できる', async(async () => {
+			await request('/following/create', {
+				userId: alice.id
+			}, bob);
+
+			const res = await request('/following/delete', {
+				userId: alice.id
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+		}));
+
+		it('フォローしていない場合は怒る', async(async () => {
+			const res = await request('/following/delete', {
+				userId: alice.id
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しないユーザーはフォロー解除できない', async(async () => {
+			const res = await request('/following/delete', {
+				userId: '000000000000000000000000'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('自分自身はフォロー解除できない', async(async () => {
+			const res = await request('/following/delete', {
+				userId: alice.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('空のパラメータで怒られる', async(async () => {
+			const res = await request('/following/delete', {}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/following/delete', {
+				userId: 'kyoppie'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('drive', () => {
+		it('ドライブ情報を取得できる', async(async () => {
+			await uploadFile({
+				userId: alice.id,
+				size: 256
+			});
+			await uploadFile({
+				userId: alice.id,
+				size: 512
+			});
+			await uploadFile({
+				userId: alice.id,
+				size: 1024
+			});
+			const res = await request('/drive', {}, alice);
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			expect(res.body).have.property('usage').eql(1792);
+		}));
+	});
+
+	describe('drive/files/create', () => {
+		it('ファイルを作成できる', async(async () => {
+			const res = await uploadFile(alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, 'Lenna.png');
+		}));
+
+		it('ファイルに名前を付けられる', async(async () => {
+			const res = await assert.request(server)
+				.post('/drive/files/create')
+				.field('i', alice.token)
+				.field('name', 'Belmond.png')
+				.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
+
+			expect(res).have.status(200);
+			expect(res.body).be.a('object');
+			expect(res.body).have.property('name').eql('Belmond.png');
+		}));
+
+		it('ファイル無しで怒られる', async(async () => {
+			const res = await request('/drive/files/create', {}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('SVGファイルを作成できる', async(async () => {
+			const res = await uploadFile(alice, __dirname + '/resources/image.svg');
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, 'image.svg');
+			assert.strictEqual(res.body.type, 'image/svg+xml');
+		}));
+	});
+
+	describe('drive/files/update', () => {
+		it('名前を更新できる', async(async () => {
+			const file = await uploadFile(alice);
+			const newName = 'いちごパスタ.png';
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				name: newName
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, newName);
+		}));
+
+		it('他人のファイルは更新できない', async(async () => {
+			const file = await uploadFile(bob);
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				name: 'いちごパスタ.png'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('親フォルダを更新できる', async(async () => {
+			const file = await uploadFile(alice);
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: folder.id
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.folderId, folder.id);
+		}));
+
+		it('親フォルダを無しにできる', async(async () => {
+			const file = await uploadFile(alice);
+
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: folder.id
+			}, alice);
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: null
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.folderId, null);
+		}));
+
+		it('他人のフォルダには入れられない', async(async () => {
+			const file = await uploadFile(alice);
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, bob)).body;
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: folder.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しないフォルダで怒られる', async(async () => {
+			const file = await uploadFile(alice);
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: '000000000000000000000000'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('不正なフォルダIDで怒られる', async(async () => {
+			const file = await uploadFile(alice);
+
+			const res = await request('/drive/files/update', {
+				fileId: file.id,
+				folderId: 'foo'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('ファイルが存在しなかったら怒る', async(async () => {
+			const res = await request('/drive/files/update', {
+				fileId: '000000000000000000000000',
+				name: 'いちごパスタ.png'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('間違ったIDで怒られる', async(async () => {
+			const res = await request('/drive/files/update', {
+				fileId: 'kyoppie',
+				name: 'いちごパスタ.png'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('drive/folders/create', () => {
+		it('フォルダを作成できる', async(async () => {
+			const res = await request('/drive/folders/create', {
+				name: 'test'
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, 'test');
+		}));
+	});
+
+	describe('drive/folders/update', () => {
+		it('名前を更新できる', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				name: 'new name'
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.name, 'new name');
+		}));
+
+		it('他人のフォルダを更新できない', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, bob)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				name: 'new name'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('親フォルダを更新できる', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const parentFolder = (await request('/drive/folders/create', {
+				name: 'parent'
+			}, alice)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: parentFolder.id
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.parentId, parentFolder.id);
+		}));
+
+		it('親フォルダを無しに更新できる', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const parentFolder = (await request('/drive/folders/create', {
+				name: 'parent'
+			}, alice)).body;
+			await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: parentFolder.id
+			}, alice);
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: null
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.parentId, null);
+		}));
+
+		it('他人のフォルダを親フォルダに設定できない', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const parentFolder = (await request('/drive/folders/create', {
+				name: 'parent'
+			}, bob)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: parentFolder.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('フォルダが循環するような構造にできない', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const parentFolder = (await request('/drive/folders/create', {
+				name: 'parent'
+			}, alice)).body;
+			await request('/drive/folders/update', {
+				folderId: parentFolder.id,
+				parentId: folder.id
+			}, alice);
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: parentFolder.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('フォルダが循環するような構造にできない(再帰的)', async(async () => {
+			const folderA = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const folderB = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			const folderC = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+			await request('/drive/folders/update', {
+				folderId: folderB.id,
+				parentId: folderA.id
+			}, alice);
+			await request('/drive/folders/update', {
+				folderId: folderC.id,
+				parentId: folderB.id
+			}, alice);
+
+			const res = await request('/drive/folders/update', {
+				folderId: folderA.id,
+				parentId: folderC.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('フォルダが循環するような構造にできない(自身)', async(async () => {
+			const folderA = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folderA.id,
+				parentId: folderA.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しない親フォルダを設定できない', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: '000000000000000000000000'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('不正な親フォルダIDで怒られる', async(async () => {
+			const folder = (await request('/drive/folders/create', {
+				name: 'test'
+			}, alice)).body;
+
+			const res = await request('/drive/folders/update', {
+				folderId: folder.id,
+				parentId: 'foo'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しないフォルダを更新できない', async(async () => {
+			const res = await request('/drive/folders/update', {
+				folderId: '000000000000000000000000'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('不正なフォルダIDで怒られる', async(async () => {
+			const res = await request('/drive/folders/update', {
+				folderId: 'foo'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('messaging/messages/create', () => {
+		it('メッセージを送信できる', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: bob.id,
+				text: 'test'
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
+			assert.strictEqual(res.body.text, 'test');
+		}));
+
+		it('自分自身にはメッセージを送信できない', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: alice.id,
+				text: 'Yo'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('存在しないユーザーにはメッセージを送信できない', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: '000000000000000000000000',
+				text: 'test'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('不正なユーザーIDで怒られる', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: 'foo',
+				text: 'test'
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('テキストが無くて怒られる', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: bob.id
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+
+		it('文字数オーバーで怒られる', async(async () => {
+			const res = await request('/messaging/messages/create', {
+				userId: bob.id,
+				text: '!'.repeat(1001)
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		}));
+	});
+
+	describe('notes/replies', () => {
+		it('自分に閲覧権限のない投稿は含まれない', async(async () => {
+			const alicePost = await post(alice, {
+				text: 'foo'
+			});
+
+			await post(bob, {
+				replyId: alicePost.id,
+				text: 'bar',
+				visibility: 'specified',
+				visibleUserIds: [alice.id]
+			});
+
+			const res = await request('/notes/replies', {
+				noteId: alicePost.id
+			}, carol);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 0);
+		}));
+	});
+
+	describe('notes/timeline', () => {
+		it('フォロワー限定投稿が含まれる', async(async () => {
+			await request('/following/create', {
+				userId: alice.id
+			}, bob);
+
+			const alicePost = await post(alice, {
+				text: 'foo',
+				visibility: 'followers'
+			});
+
+			const res = await request('/notes/timeline', {}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 1);
+			assert.strictEqual(res.body[0].id, alicePost.id);
+		}));
+	});
+});
+*/
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index aa7a66f86..c1932a905 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1013,6 +1013,16 @@ ajv-keywords@^3.5.2:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
+ajv@8.10.0:
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d"
+  integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    json-schema-traverse "^1.0.0"
+    require-from-string "^2.0.2"
+    uri-js "^4.2.2"
+
 ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
   version "6.12.5"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"