diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts
index 77a445e186..128c1a98d2 100644
--- a/src/server/api/api-handler.ts
+++ b/src/server/api/api-handler.ts
@@ -1,12 +1,12 @@
 import * as Koa from 'koa';
 
-import Endpoint from './endpoint';
+import { IEndpoint } from './endpoints';
 import authenticate from './authenticate';
 import call from './call';
 import { IUser } from '../../models/user';
 import { IApp } from '../../models/app';
 
-export default async (endpoint: Endpoint, ctx: Koa.Context) => {
+export default async (endpoint: IEndpoint, ctx: Koa.Context) => {
 	const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body;
 
 	const reply = (x?: any, y?: any) => {
@@ -37,7 +37,7 @@ export default async (endpoint: Endpoint, ctx: Koa.Context) => {
 
 	// API invoking
 	try {
-		res = await call(endpoint, user, app, body, (ctx.req as any).file);
+		res = await call(endpoint.name, user, app, body, (ctx.req as any).file);
 	} catch (e) {
 		reply(400, e);
 		return;
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index eb3e292dc1..769ff95acd 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -1,35 +1,12 @@
-import * as path from 'path';
-import * as glob from 'glob';
-
-import Endpoint from './endpoint';
 import limitter from './limitter';
 import { IUser } from '../../models/user';
 import { IApp } from '../../models/app';
+import endpoints from './endpoints';
 
-const files = glob.sync('**/*.js', {
-	cwd: path.resolve(__dirname + '/endpoints/')
-});
-
-const endpoints: Array<{
-	exec: any,
-	meta: Endpoint
-}> = files.map(f => {
-	const ep = require('./endpoints/' + f);
-
-	ep.meta = ep.meta || {};
-	ep.meta.name = f.replace('.js', '');
-
-	return {
-		exec: ep.default,
-		meta: ep.meta
-	};
-});
-
-export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => {
+export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => {
 	const isSecure = user != null && app == null;
 
-	const epName = typeof endpoint === 'string' ? endpoint : endpoint.name;
-	const ep = endpoints.find(e => e.meta.name === epName);
+	const ep = endpoints.find(e => e.name === endpoint);
 
 	if (ep.meta.secure && !isSecure) {
 		return rej('ACCESS_DENIED');
@@ -51,7 +28,7 @@ export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any,
 
 	if (ep.meta.requireCredential && ep.meta.limit) {
 		try {
-			await limitter(ep.meta, user); // Rate limit
+			await limitter(ep, user); // Rate limit
 		} catch (e) {
 			// drop request if limit exceeded
 			return rej('RATE_LIMIT_EXCEEDED');
diff --git a/src/server/api/endpoint.ts b/src/server/api/endpoints.ts
similarity index 78%
rename from src/server/api/endpoint.ts
rename to src/server/api/endpoints.ts
index 5936a85033..cc9b90d6fc 100644
--- a/src/server/api/endpoint.ts
+++ b/src/server/api/endpoints.ts
@@ -1,9 +1,7 @@
-export default interface IEndpoint {
-	/**
-	 * エンドポイント名
-	 */
-	name: string;
+import * as path from 'path';
+import * as glob from 'glob';
 
+export interface IEndpointMeta {
 	/**
 	 * このエンドポイントにリクエストするのにユーザー情報が必須か否か
 	 * 省略した場合は false として解釈されます。
@@ -58,3 +56,25 @@ export default interface IEndpoint {
 	 */
 	kind?: string;
 }
+
+export interface IEndpoint {
+	name: string;
+	exec: any;
+	meta: IEndpointMeta;
+}
+
+const files = glob.sync('**/*.js', {
+	cwd: path.resolve(__dirname + '/endpoints/')
+});
+
+const endpoints: IEndpoint[] = files.map(f => {
+	const ep = require('./endpoints/' + f);
+
+	return {
+		name: f.replace('.js', ''),
+		exec: ep.default,
+		meta: ep.meta || {}
+	};
+});
+
+export default endpoints;
diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts
index 401ec23d82..c5eb29e1af 100644
--- a/src/server/api/endpoints/drive/files/create.ts
+++ b/src/server/api/endpoints/drive/files/create.ts
@@ -1,8 +1,36 @@
 import * as fs from 'fs';
+const ms = require('ms');
 import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
 import { validateFileName, pack } from '../../../../../models/drive-file';
 import create from '../../../../../services/drive/add-file';
 import { ILocalUser } from '../../../../../models/user';
+import getParams from '../../../get-params';
+
+export const meta = {
+	desc: {
+		ja: 'ドライブにファイルをアップロードします。'
+	},
+
+	requireCredential: true,
+
+	limit: {
+		duration: ms('1hour'),
+		max: 100
+	},
+
+	withFile: true,
+
+	kind: 'drive-write',
+
+	params: {
+		folderId: $.type(ID).optional.nullable.note({
+			default: null,
+			desc: {
+				ja: 'フォルダID'
+			}
+		})
+	}
+};
 
 /**
  * Create a file
@@ -27,17 +55,19 @@ export default async (file: any, params: any, user: ILocalUser): Promise<any> =>
 		name = null;
 	}
 
-	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
-	if (folderIdErr) throw 'invalid folderId param';
-
 	function cleanup() {
 		fs.unlink(file.path, () => {});
 	}
 
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) {
+		cleanup();
+		throw psErr;
+	}
+
 	try {
 		// Create file
-		const driveFile = await create(user, file.path, name, null, folderId);
+		const driveFile = await create(user, file.path, name, null, ps.folderId);
 
 		cleanup();
 
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 7125d02de9..66d018618c 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -8,8 +8,6 @@ import { IApp } from '../../../../models/app';
 import getParams from '../../get-params';
 
 export const meta = {
-	name: 'notes/create',
-
 	desc: {
 		ja: '投稿します。'
 	},
diff --git a/src/server/api/index.ts b/src/server/api/index.ts
index 004c21b821..e89988efd0 100644
--- a/src/server/api/index.ts
+++ b/src/server/api/index.ts
@@ -35,7 +35,7 @@ const router = new Router();
 /**
  * Register endpoint handlers
  */
-endpoints.forEach(endpoint => endpoint.withFile
+endpoints.forEach(endpoint => endpoint.meta.withFile
 	? router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint))
 	: router.post(`/${endpoint.name}`, handler.bind(null, endpoint))
 );
diff --git a/src/server/api/limitter.ts b/src/server/api/limitter.ts
index bd30685ade..20a18a7098 100644
--- a/src/server/api/limitter.ts
+++ b/src/server/api/limitter.ts
@@ -1,14 +1,14 @@
 import * as Limiter from 'ratelimiter';
 import * as debug from 'debug';
 import limiterDB from '../../db/redis';
-import Endpoint from './endpoint';
+import { IEndpoint } from './endpoints';
 import getAcct from '../../misc/acct/render';
 import { IUser } from '../../models/user';
 
 const log = debug('misskey:limitter');
 
-export default (endpoint: Endpoint, user: IUser) => new Promise((ok, reject) => {
-	const limitation = endpoint.limit;
+export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => {
+	const limitation = endpoint.meta.limit;
 
 	const key = limitation.hasOwnProperty('key')
 		? limitation.key
diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts
index 76516b445a..3e6ee0f12c 100644
--- a/src/server/web/docs.ts
+++ b/src/server/web/docs.ts
@@ -168,14 +168,15 @@ router.get('/assets/*', async ctx => {
 
 router.get('/*/api/endpoints/*', async ctx => {
 	const lang = ctx.params[0];
-	const ep = require('../../../built/server/api/endpoints/' + ctx.params[1]).meta;
+	const name = ctx.params[1];
+	const ep = require('../../../built/server/api/endpoints/' + name).meta || {};
 
 	const vars = {
-		title: ep.name,
-		endpoint: ep.name,
+		title: name,
+		endpoint: name,
 		url: {
 			host: config.api_url,
-			path: ep.name
+			path: name
 		},
 		desc: ep.desc,
 		// @ts-ignore