봇용 서버 API
Millida Hosting 클라이언트 API를 알아보세요: 키 발급, 권한, 자체 봇이나 스크립트에서의 서버 관리와 콘솔 스트리밍.
업데이트 2026년 10월 13일
제어판에서 할 수 있는 모든 작업을 직접 작성한 코드로도 할 수 있어요. 일정에 따라 서버를 시작하고, Discord에 상태를 표시하고, 봇에서 명령을 보낼 수 있죠.
키
API는 유료 요금제라면 어디서든 사용할 수 있어요. 파일, SFTP, 팀 액세스가 있는 바로 그곳이에요. 무료 요금제에서는 키가 발급되지 않아요. 키는 동일한 공유 액세스를 봇용으로 제공하는 것뿐이거든요.
"플레이어 및 액세스" 섹션 → "API 키" → "발급"을 선택하세요. 키로 무엇을 할 수 있는지 확인하세요. 권한은 공유 액세스와 동일해요.
키는 한 번만 표시돼요. 저희는 지문만 저장하거든요. 분실했다면 새로 발급하고 이전 키는 폐기하세요.
키는 Authorization: Bearer mhk_live_... 헤더로 전달해요.
메서드
기본 URL — https://millida.net/v2/hosting/api.
| 메서드 | 하는 일 | 필요한 권한 |
|---|---|---|
GET /server | 서버 카드 | console |
GET /status | 상태 및 온라인 | console |
POST /start | 시작 | start |
POST /stop | 중지 | start |
POST /restart | 재시작 | start |
POST /command | 콘솔에 명령 전송 | console |
GET /console | 실시간 로그 (SSE) | console |
GET /files?path= | 파일 목록 | files |
GET /files/configs | 폼에서 읽는 구성 파일 | files |
GET /files/config?path= | 타입이 포함된 파일 설정 | files |
POST /files/config | 파일 설정 변경 | files |
GET /backups | 백업 목록 | backups |
POST /backups | 백업 생성 | backups |
GET /antibot | 안티봇 설정, 백엔드, 방화벽 규칙 | protection |
PUT /antibot | 보호 설정 변경 | protection |
GET /antibot/overview?range= | hour, day, week 기준 연결 및 차단 | protection |
POST /antibot/backends | 백엔드 추가 {"address":"host:port","label":"..."} | protection |
DELETE /antibot/backends/{id} | 백엔드 제거 | protection |
POST /antibot/rules | 방화벽 규칙 {"kind":"allow"|"deny","cidr":"1.2.3.0/24"} | protection |
DELETE /antibot/rules/{id} | 규칙 제거 | protection |
POST /antibot/verify-domain/check | 인증 페이지 도메인의 DNS 확인 | protection |
예시: 서버를 시작하고 온라인이 될 때까지 기다리기
KEY="mhk_live_xxx"
API="https://millida.net/v2/hosting/api"
curl -sX POST "$API/start" -H "Authorization: Bearer $KEY"
until curl -s "$API/status" -H "Authorization: Bearer $KEY" | grep -q RUNNING; do
sleep 5
done
echo "server online"
백업은 백그라운드에서 생성돼요
POST /backups는 즉시 {"id":"..."}를 반환하지만, 아카이브 자체는 노드에서 조립돼요. 몇 기가바이트짜리 월드는 몇 분이 걸리죠. GET /backups에서 status 필드로 준비 상태를 확인하세요. pending — 아직 조립 중, ready — 스토리지에 저장됨(이때 실제 sizeMb가 나타나요), failed — 조립 실패.
ID=$(curl -sX POST "$API/backups" -H "Authorization: Bearer $KEY" | jq -r .id)
until curl -s "$API/backups" -H "Authorization: Bearer $KEY" \
| jq -e --arg id "$ID" '.[] | select(.id==$id) | select(.status=="ready")' >/dev/null; do
sleep 15
done
echo "backup ready"
백업이 진행 중인 동안에는 같은 서버에서 두 번째 백업을 만들 수 없어요. 노드는 한 번에 하나씩 처리하거든요.
실시간 콘솔
GET /console은 Server-Sent Events 스트림을 반환해요. 각 줄은 data: 접두사와 함께 도착하죠. WebSocket은 필요 없어요. SSE는 어떤 프록시든 통과하니까요.
알아두면 좋은 점
- 모든 키 작업은 서버 로그에 "API 키 N"이라는 작성자와 함께 기록돼요. 사람과 봇이 모두 서버를 운영할 때 바로 이게 필요하죠.
- 속도 제한은 주소가 아니라 키 단위로 계산돼요. IP를 바꿔도 제한을 우회할 수 없고, 사무실 네트워크의 옆자리 사람이 제한을 소모하지도 않아요. 일반적인 자동화에는 넉넉하지만, 1초마다 상태를 폴링하면 제한에 걸려요. 10~15초에 한 번이면 충분해요. 콘솔 스트림은 분당 최대 열 번까지 열 수 있어요. 메시지마다 새로 여는 대신 연결 하나를 유지하세요.
- 서버가 정지되거나 요금제가 더 이상 유료가 아니면 키는 즉시 작동을 멈춰요. 그 경우 수동으로 폐기할 필요는 없어요.
- 서버당 최대 다섯 개의 키. 필요 없는 키는 폐기하세요.
안티봇 보호
GET /antibot은 두 필드로 설정을 반환해요. config — 서버 자체 설정이고, 아직 존재하지 않으면 null이며 서버는 기본값(default 필드)으로 보호돼요. effective는 항상 존재해요. 현재 적용 중이거나 첫 수정 후 서버가 사용하게 될 설정이죠. 여기서 값을 읽으세요.
PUT /antibot은 변경하려는 필드만 받아요.
curl -sX PUT "$API/antibot" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"preset":"balanced"}'
레벨(preset): off, under_attack, balanced, aggressive, custom. 레벨은 검사 값을 스스로 확장하므로, 수동 구성에는 custom을 선택하고 검사를 보내세요. checkCaptcha, checkPhysics, checkKeepAlive, checkTeleport, checkBrand, checkPing, checkTransfer, checkClientSettings — 각각 off, under_attack, always 중 하나예요. 주소 목록(vpnMode, datacenterMode, torMode) — off, log, block. 지역 필터 — geoMode(off, allow, deny)와 geoCountries(국가 코드, 예: ["RU","BY"]).
포워딩 시크릿은 API로 발급하거나 변경하지 않아요. 소유자에게만 제어판에 표시돼요.
구성 설정
GET /files/config?path=server.properties는 파싱된 파일을 반환해요. 각 항목에 타입, 현재 값, "재시작 필요" 플래그가 함께 나오죠.
{
"path": "server.properties",
"format": "properties",
"options": [
{ "key": "pvp", "type": "boolean", "value": "true", "owner": "sync", "requiresRestart": false },
{ "key": "difficulty", "type": "select", "value": "normal", "values": ["peaceful", "easy", "normal", "hard"], "owner": "sync", "requiresRestart": false },
{ "key": "server-port", "type": "integer", "value": "25565", "owner": "platform", "requiresRestart": true }
]
}
owner는 해당 항목으로 무엇을 할 수 있는지 알려줘요. platform — 값이 호스팅에서 설정되며 수정은 거부돼요. sync — 항목이 제어판과 파일 양쪽에 존재해요. file — 파일만 알고 있는 항목이죠.
쓰기는 보내는 키만 변경하고, 파일의 나머지는 그대로 유지돼요.
curl -sX POST "$API/files/config" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"path":"server.properties","values":{"pvp":"false","difficulty":"hard"}}'
폼은 .properties, .yml/.yaml, .toml을 읽어요. 제어판이 전혀 모르는 플러그인 구성 파일도 포함되죠. 필드 타입은 값에서 추론해요. 중첩 키는 점으로 구분해요. settings.bungeecord, advanced.compression-threshold.
전체 파일은 여전히 GET/PUT /files/content?path=로 사용할 수 있어요. 두 경로 모두 같은 곳에 쓰거든요.
Node.js와 Python으로 만든 바로 사용 가능한 클라이언트
아래는 최소한의 클라이언트예요. 상태를 확인하고, 서버를 시작하고, 채팅에 메시지를 보내요. 키는 MILLIDA_KEY 환경 변수에서 가져와요. git에 올라가는 코드에 키를 붙여 넣지 마세요. 오류 응답은 message 필드가 있는 JSON이에요. 봇이 기록해야 하는 값이 바로 이것이에요.
Node.js 18+ (내장 fetch):
const API = 'https://millida.net/v2/hosting/api';
const KEY = process.env.MILLIDA_KEY;
async function call(method, path, body) {
const res = await fetch(`${API}${path}`, {
method,
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(`${method} ${path}: ${res.status} ${await res.text()}`);
return res.status === 204 ? null : res.json();
}
const status = await call('GET', '/status');
if (status.status !== 'RUNNING') await call('POST', '/start');
await call('POST', '/command', { command: 'say Hello from the bot' });
requests 라이브러리를 사용하는 Python 3, 그리고 실시간 콘솔 읽기:
import os
import requests
API = "https://millida.net/v2/hosting/api"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['MILLIDA_KEY']}"
def call(method, path, body=None):
res = session.request(method, API + path, json=body, timeout=15)
res.raise_for_status()
return res.json() if res.content else None
status = call("GET", "/status")
if status["status"] != "RUNNING":
call("POST", "/start")
call("POST", "/command", {"command": "say Hello from the bot"})
with session.get(API + "/console", stream=True, timeout=None) as stream:
for line in stream.iter_lines(decode_unicode=True):
if line and line.startswith("data:"):
print(line[5:].strip())
