API 참조
REST APIs, 스마트 계약 인터페이스, GraphQL 쿼리 및 WebSocket 채널을 다루는 CloudBank 예측 시장에 대한 포괄적인 API 문서입니다.
Authentication
사용자 인증(EIP-191 서명)
1. POST /api/v1/auth/nonce → get challenge nonce
2. sign message with wallet → EIP-191 personal_sign
3. POST /api/v1/auth/login → submit signature and obtain JWT
4. Authorization: Bearer {token} → include in subsequent requestsJWT은(는) HS256로 서명되었습니다. 클레임에는 JWT(지갑 주소), HS256 및 Subject이 포함됩니다.
관리자 인증
- JWT 전달자: JWT 액세스 권한 획득 + 새로 고침 토큰
- 기본 인증: 부트스트랩 단계
Authorization: Basic base64(user:pass)중에 사용됩니다. - RBAC: RBAC(전체 권한) /
superadmin(제한된 권한)
REST API
기본 URL: URL
빠른 cURL 예(인증/시장/거래/지갑)
export HOST="https://docs-test.cloudbank.to"
export API="$HOST/api/v1"
export ADDRESS="0xYourWalletAddress"1) 인증: nonce + 로그인
curl -sS -X POST "$API/auth/nonce" \
-H "Content-Type: application/json" \
-d "{\"address\":\"$ADDRESS\"}"{
"nonce": "7fa3f1d9",
"message": "CloudBank login nonce: 7fa3f1d9",
"expiresAt": "2026-03-06T00:00:00Z"
}서명 후 로그인 제출($SIGNATURE 및 $MESSAGE은 실제 지갑 서명 데이터로 대체되어야 함):
export MESSAGE="CloudBank login nonce: 7fa3f1d9"
export SIGNATURE="0xYourEIP191Signature"
curl -sS -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d "{\"address\":\"$ADDRESS\",\"message\":\"$MESSAGE\",\"signature\":\"$SIGNATURE\"}"{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"custodialWalletAddress": "0x4D7E...A9f2",
"expiresAt": "2026-03-06T12:00:00Z"
}export TOKEN="eyJhbGciOi..."2) 시장: 시장 목록 조회
curl -sS "$API/markets?state=TRADING&limit=20&offset=0" \
-H "Authorization: Bearer $TOKEN"{
"items": [
{
"id": "0x2E79B7190c463CD11793520d23F63A3d035A94c2",
"question": "Will BTC close above $80k on Friday?",
"state": "TRADING",
"closeTime": "2026-03-08T12:00:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}3) 거래: 주문 제출
curl -sS -X POST "$API/orderbook/orders" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"marketId":"0x2E79B7190c463CD11793520d23F63A3d035A94c2",
"outcome":"YES",
"side":"BUY",
"orderType":"LIMIT",
"price":"0.62",
"amount":"100"
}'{
"orderHash": "0x8f7b...c31d",
"status": "open",
"filledAmount": "0",
"createdAt": "2026-03-05T10:00:00Z"
}4) 지갑: 정보 얻기 + 출금
curl -sS "$API/wallet/info" \
-H "Authorization: Bearer $TOKEN"{
"walletAddress": "0x4D7E...A9f2",
"balances": {
"BNB": "0.132",
"USDC": "1250.50"
},
"chainId": 97
}curl -sS -X POST "$API/wallet/withdraw" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"asset":"USDC",
"toAddress":"0xReceiverAddress",
"amount":"25.0"
}'{
"txHash": "0x4b17...9f25",
"asset": "USDC",
"amount": "25.0",
"status": "pending"
}Auth
| Method | Path | Auth | 비율 제한 | Description |
|---|---|---|---|---|
| POST | POST | - | 100/min/IP | EIP-191 챌린지 생성(주소 -> nonce + 메시지) |
| POST | POST | - | 5/min/IP | 서명을 확인하고 JWT + custodialWalletAddress를 반환합니다. |
POST POST
// Request
{ "address": "0x..." }
// Response
{ "nonce": "abc123", "message": "Sign this message...", "expiresAt": "2026-01-01T00:00:00Z" }POST POST
// Request
{ "address": "0x...", "signature": "0x...", "message": "Sign this message..." }
// Response
{ "token": "eyJ...", "custodialWalletAddress": "0x...", "expiresAt": "2026-01-01T00:00:00Z" }Wallet
| Method | Path | Auth | 비율 제한 | Description |
|---|---|---|---|---|
| GET | GET | JWT | 100/min | 관리 지갑 정보 얻기 |
| DELETE | DELETE | JWT | 100/min | 수탁 지갑 삭제(비기능 상태 필요) |
| POST | POST | JWT | 20/min | 대신 거래 서명(화이트리스트에 포함된 계약) |
| POST | POST | JWT | 3/min | BNB/USDC 출금(일일 한도) |
POST POST
// Request
{ "to": "0x...", "data": "0x...", "value": "0", "product": "predict" }
// Response
{ "txHash": "0x...", "gasMode": "paymaster", "status": "pending", "createdAt": "..." }POST POST
// Request
{ "asset": "USDC", "toAddress": "0x...", "amount": "100.5" }
// Response
{ "txHash": "0x...", "asset": "USDC", "amount": "100.5", "fee": "0", "gasMode": "user", "status": "pending" }Orderbook
| Method | Path | Auth | 비율 제한 | Description |
|---|---|---|---|---|
| GET | GET | - | 120/min/IP | 주문서 깊이 얻기 |
| POST | POST | JWT | 30/min | 주문 제출 |
| POST | POST | JWT | 30/min | 하이브리드 경로(AMM + 주문서) |
| DELETE | DELETE | JWT | 30/min | 주문 취소 |
| GET | GET | JWT | 30/min | 하트비트 상태 가져오기 |
| GET | GET | JWT | - | WebSocket 하트비트 채널 |
GET GET
?marketId={id}&outcomeTokenId={tokenId}&depth=20// Response
{ "market": "0x...", "bids": [{"price": "0.65", "size": "100"}], "asks": [...] }관계 초대
| Method | Path | Auth | 비율 제한 | Description |
|---|---|---|---|---|
| GET | GET | - | 120/min/IP | 초대자별 쿼리 관계 |
| POST | POST | JWT | 60/min | 초대 관계 바인딩 |
| GET | GET | JWT | 60/min | 자신의 초대 관계를 얻으십시오 |
관리자 — 인증
| Method | Path | Auth | 비율 제한 | Description |
|---|---|---|---|---|
| POST | POST | - | 30/min/IP | 관리자 로그인 |
| POST | POST | - | 30/min/IP | 새로고침 토큰 |
관리자 — 사용자(수퍼관리자)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | GET | 관리자+SA | 모든 관리자 나열 |
| GET | GET | 관리자+SA | 관리자 세부정보 가져오기 |
| POST | POST | 관리자+SA | 관리자 만들기 |
| PATCH | PATCH | 관리자+SA | 관리자 업데이트 |
| POST | POST | 관리자+SA | 비밀번호 재설정 |
| POST | POST | 관리자+SA | 바인딩 작업 지갑 |
| POST | POST | 관리자+SA | 작업 지갑 바인딩 해제 |
관리자 — 관리 사용자
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | GET | Admin | 관리 사용자를 위한 페이지로 매겨진 쿼리 |
| GET | GET | Admin | 사용자 세부정보 + 지갑 가져오기 |
| DELETE | DELETE | SA | 사용자 지갑 영구 삭제 |
관리자 — 지갑 운영(수퍼관리자)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | GET | SA | 목록 작업 지갑 |
| GET | GET | SA | 지갑 세부정보 가져오기 |
| POST | POST | SA | 운영 지갑 생성 |
| PATCH | PATCH | SA | 업데이트 작업 지갑 |
| POST | POST | Admin | 운영 지갑 서명 대리 거래 |
관리자 — 화이트리스트
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | POST | Admin | 계약 화이트리스트 추가 |
| GET | GET | Admin | 쿼리 화이트리스트(제품별로 필터링 가능) |
| PATCH | PATCH | Admin | 화이트리스트 항목 업데이트 |
| DELETE | DELETE | Admin | 화이트리스트 항목 삭제 |
관리자 — 관계 초대(수퍼관리자)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | GET | SA | 페이지를 매긴 쿼리(CSV 내보내기 지원) |
| POST | POST | SA | 대량 가져오기(배치당 <=500행) |
System
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | GET | - | 상태 확인(SQL/Redis/BSC 상태) |
| GET | GET | - | 프로메테우스 측정항목 |
오류 코드
모든 오류 응답은 다음 형식을 따릅니다.
{ "code": "error_code", "message": "Human-readable message", "details": {} }| Code | HTTP 상태 | Description |
|---|---|---|
invalid_request | 400 | 잘못된 요청 매개변수 |
invalid_signature | 401 | EIP-191 서명 확인 실패 |
invalid_nonce | 401 | Nonce가 유효하지 않거나 만료되었습니다. |
unauthorized | 401 | 인증되지 않았거나 토큰이 만료되었습니다. |
forbidden | 403 | Forbidden |
contract_not_whitelisted | 403 | 계약이 허용 목록에 없습니다. |
insufficient_bnb | 400 | BNB 잔액 부족 |
insufficient_balance | 400 | 잔액 부족 |
daily_limit_exceeded | 429 | 일일 출금 한도 초과 |
not_found | 404 | 리소스를 찾을 수 없습니다. |
rate_limited | 429 | 비율 제한을 초과했습니다. |
invite_binding_conflict | 409 | 초대 관계 갈등 |
invite_request_replayed | 409 | 중복 바인딩 요청 |
invite_self_not_allowed | 400 | 자체 초대는 허용되지 않습니다. |
internal_error | 500 | 내부 서버 오류 |
service_unavailable | 503 | 서비스 이용 불가 |
WebSocket
주문장 하트비트
URL: URL 인증: 전달자 JWT(쿼리 매개변수 또는 헤더)
서버 → 클라이언트
{ "type": "heartbeat_connected", "status": { ... } }
{ "type": "heartbeat_ack", "status": { ... } }
{ "type": "error", "message": "Error description" }클라이언트 → 서버
- 모든 메시지는 하트비트 연결 유지를 트리거할 수 있습니다.
- 표준 WebSocket ping/pong 지원
스마트 계약 인터페이스
OptimisticController
라이프사이클 상태를 관리하는 이진 예측 시장 컨트롤러: TRADING -> PROPOSED -> DISPUTED -> RESOLVED / CANCELLED.
// Read methods
function getQuestion() external view returns (string memory);
function getMetadata() external view returns (string memory);
function getState() external view returns (MarketState);
function getConditionId() external view returns (bytes32);
function getPositionIds() external view returns (uint256[] memory);
function isActive() external view returns (bool);
function canSettle() external view returns (bool);
function getFeatureFlags() external pure returns (uint256);
// Market management (onlyOwner)
function setMetadata(string calldata metadata) external;
function setOracleAskAfter(uint256 newAskAfter) external;
function depositStake() external;
function proposeOutcome(uint8 outcome, bytes calldata data) external;
function settle(bytes calldata data) external;
function cancelAsInvalid() external;
// Open operations
function disputeOutcome(bytes calldata data) external;
function requestOracleQuestion(bytes calldata data) external payable;
function refundOracleQuestion() external;
function proposeOutcomeFromOracle(bytes calldata data) external;이벤트:
OutcomeProposed(uint8 indexed outcome, address indexed proposer, uint256 timestamp, bytes32 dataHash)OutcomeDisputed(address indexed disputer, uint256 timestamp, bytes32 dataHash)OracleQuestionRequested(bytes32 indexed oracleQuestionId, uint256 deadline, address indexed requester, uint256 value, bytes32 dataHash)StakeProcessed(address indexed creator, address indexed token, uint256 amount, bool refunded)StateTransition(MarketState indexed from, MarketState indexed to)MarketResolved(uint8 indexed outcome, uint256 timestamp)
BinaryCPMM
지속적인 제품 시장 조성자(x*y=k), Gnosis CTF ERC-1155 포지션 토큰을 기준으로 한 수수료 0%.
// Read methods
function getReserves() external view returns (uint256 yesReserves, uint256 noReserves);
function getPrice(uint256 tokenId) external view returns (uint256 price1e18);
function quote(uint256 tokenIn, uint256 amountIn) public view returns (uint256 amountOut);
// Liquidity
function addLiquidity(uint256 collateralAmount) external returns (uint256 lpOut);
function removeLiquidity(uint256 lpIn) external returns (uint256 yesOut, uint256 noOut);
// Trading
function swap(uint256 tokenIn, uint256 amountIn, uint256 minOut) external returns (uint256 amountOut);
function swapCollateralForOutcome(uint256 outcomeTokenId, uint256 collateralAmount, uint256 minOutcomeOut) external returns (uint256 outcomeOut);
function sellOutcomeForCollateral(uint256 outcomeTokenId, uint256 outcomeIn, uint256 minCollateralOut) external returns (uint256 collateralOut);
function sellAllOutcomeForCollateral(uint256 outcomeTokenId, uint256 minCollateralOut) external returns (uint256 collateralOut);이벤트:
LiquidityAdded(address indexed provider, uint256 collateralIn, uint256 lpOut)LiquidityRemoved(address indexed provider, uint256 lpIn, uint256 yesOut, uint256 noOut)Swapped(address indexed trader, uint256 indexed tokenIn, uint256 amountIn, uint256 indexed tokenOut, uint256 amountOut)CollateralSwappedForOutcome(address indexed trader, uint256 collateralIn, uint256 indexed outcomeTokenId, uint256 outcomeOut)OutcomeSoldForCollateral(address indexed trader, uint256 indexed outcomeTokenId, uint256 outcomeIn, uint256 collateralOut)
DCPPFactory
EIP-1167 최소 프록시를 통해 시장 + AMM을 배포하는 시장 공장.
// Create market
function createOptimisticMarket(string calldata question, uint256 livenessPeriod, address, uint256, uint8 outcomeSlots) external returns (address market);
function createOptimisticMarket_V2(string calldata question, uint256 livenessPeriod, address, uint256, uint8 outcomeSlots) external returns (address market);
function createOptimisticMarketWithInitialLiquidity(...) external returns (address market, address amm);
function createOptimisticMarketWithInitialLiquidity_V2(...) external returns (address market, address amm);
function createOptimisticMarket_V3(string calldata question, string calldata metadata, ...) public returns (address market, address amm);
function createOptimisticMarket_V4(..., bytes32 key) external returns (address market, address amm);
// Read methods
function getMarketCount() external view returns (uint256);
function getMarketByIndex(uint256 idx) external view returns (address);
// Management (onlyOwner)
function setOracleAdapter(IYesNoOracleAdapter newAdapter) external;
function setMultiOutcomeFactory(address newFactory) external;MultiOutcomeMarket
N개의 독립적인 바이너리 시장(1대 나머지 모드)을 구성하는 다중 옵션 시장 래퍼입니다.
function optionCount() external view returns (uint256);
function getOption(uint256 index) external view returns (string label, address market, address amm, uint256 yesTokenId, uint256 noTokenId);
function proposeWinner(uint8 winnerIndex, bytes calldata data) external; // onlyOwner
function settleWinner(uint8 winnerIndex) external; // onlyOwner
// Oracle flow
function requestWinnerFromOracle(uint256 deadline, bytes calldata data) external payable returns (bytes32 qid);
function proposeWinnerFromOracle(bytes calldata data) external;
function settleWinnerFromOracle() external;SoraOracle
지정된 공급자가 응답한 최소 Yes/No oracle입니다.
function askYesNoQuestion(string calldata question, uint256 deadline) external payable returns (uint256 questionId);
function provideAnswer(uint256 questionId, bool boolAnswer, uint8 confidenceScore, string calldata dataSource) external; // onlyOracleProvider
function getQuestionWithAnswer(uint256 questionId) external view returns (string, uint256, string, bool, AnswerStatus, uint256);
function refundUnansweredQuestion(uint256 questionId) external;CloudBankVerifyingPaymaster
ERC-4337 AA 서명 확인, 일일 할당량 및 보낸 사람 허용 목록이 포함된 Paymaster.
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 maxCost) external returns (bytes memory context, uint256 validationData);
function postOp(PostOpMode, bytes calldata context, uint256 actualGasCost) external;
function getHash(UserOperation calldata userOp, uint48 validUntil, uint48 validAfter) public view returns (bytes32);
// Management (onlyOwner)
function setVerifyingSigner(address newSigner) external;
function setEnforceSenderAllowlist(bool enabled) external;
function setDailySponsorLimit(uint256 newLimit) external;
function setSenderAllowlist(address sender, bool allowed) external;
function batchSetSenderAllowlist(address[] calldata senders, bool allowed) external;
function deposit() external payable;FactoryRegistry
현재 공장 주소에 대한 안정적인 포인터를 제공하는 공장 레지스트리입니다.
function factory() external view returns (address);
function setFactory(address newFactory) external; // onlyOwner주요 인터페이스
// IMarket
interface IMarket {
function getQuestion() external view returns (string memory);
function getState() external view returns (MarketState);
function getConditionId() external view returns (bytes32);
function isActive() external view returns (bool);
function canSettle() external view returns (bool);
function settle(bytes calldata data) external;
}
// IYesNoOracleAdapter
interface IYesNoOracleAdapter {
function ask(string calldata question, uint256 deadline, bytes calldata data) external payable returns (bytes32);
function getAnswer(bytes32 oracleQuestionId) external view returns (bool finalized, uint8 outcome);
function refund(bytes32 oracleQuestionId) external returns (uint256 refunded);
}
// IConditionalTokens (Gnosis CTF)
interface IConditionalTokens {
function prepareCondition(address oracle, bytes32 questionId, uint256 outcomeSlotCount) external;
function reportPayouts(bytes32 questionId, uint256[] calldata payouts) external;
function splitPosition(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata partition, uint256 amount) external;
function mergePositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata partition, uint256 amount) external;
function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata indexSets) external;
}GraphQL (더 그래프/골드스키)
종료점: https://api.goldsky.com/api/public/project_{id}/subgraphs/cloudbank-bsc-testnet/{version}/gn
스키마 엔터티
Market
type Market @entity {
id: ID! # Market contract address
question: String! # Market question
conditionId: Bytes! # CTF condition ID
state: Int! # 0=TRADING, 1=PROPOSED, 2=RESOLVED
amm: Bytes # AMM contract address
yesTokenId: BigInt # YES token ID (ERC-1155)
noTokenId: BigInt # NO token ID (ERC-1155)
creator: Bytes! # Creator address
createdAtTimestamp: BigInt!
totalVolume: BigDecimal! # Total volume (USDC)
metadataUri: String # IPFS metadata URI
category: String # Market category (parsed from IPFS)
isMultiOutcome: Boolean! # Is multi-outcome market
multiOutcomeMarket: MultiOutcomeMarket
optionIndex: Int # Multi-outcome option index
optionLabel: String # Multi-outcome option label
trades: [Trade!]! @derivedFrom(field: "market")
}Trade
type Trade @entity {
id: ID! # txHash-logIndex
market: Market!
amm: Bytes!
trader: Bytes!
user: User!
type: String! # "buy" | "sell"
side: String! # "yes" | "no"
amountIn: BigInt!
amountOut: BigInt!
price: BigDecimal! # amountIn / amountOut
outcomeTokenId: BigInt!
timestamp: BigInt!
blockNumber: BigInt!
txHash: Bytes!
}User
type User @entity {
id: ID! # Address (lowercase)
totalVolume: BigDecimal!
realizedPnL: BigDecimal!
totalTrades: Int!
totalBuys: Int!
totalSells: Int!
totalProfitableSells: Int!
firstTradeTimestamp: BigInt!
lastTradeTimestamp: BigInt!
trades: [Trade!]! @derivedFrom(field: "user")
positions: [UserPosition!]! @derivedFrom(field: "user")
}UserPosition
type UserPosition @entity {
id: ID! # user-market-side
user: User!
market: Market!
side: String! # "yes" | "no"
shares: BigDecimal! # Current holdings
totalBought: BigDecimal!
totalSold: BigDecimal!
costBasis: BigDecimal! # Cost basis
proceeds: BigDecimal! # Cumulative proceeds
isOpen: Boolean!
openedAtTimestamp: BigInt!
lastTradeTimestamp: BigInt!
}MultiOutcomeMarket
type MultiOutcomeMarket @entity {
id: ID!
question: String!
metadataUri: String
creator: Bytes!
optionCount: Int!
options: [Market!]! @derivedFrom(field: "multiOutcomeMarket")
createdAtTimestamp: BigInt!
}일반적인 쿼리
모든 시장 보기
query GetMarkets {
markets(orderBy: createdAtTimestamp, orderDirection: desc) {
id, question, state, amm, yesTokenId, noTokenId, totalVolume, category
trades(first: 1, orderBy: timestamp, orderDirection: desc) { price, side }
}
}시장 거래 받기
query GetMarketTrades($marketId: ID!) {
trades(where: { market: $marketId }, orderBy: timestamp, orderDirection: desc, first: 100) {
id, trader, type, side, amountIn, amountOut, price, timestamp, txHash
}
}사용자 위치 가져오기
query GetUserPositions($user: ID!) {
user(id: $user) {
positions(where: { isOpen: true }) {
side, shares, costBasis, proceeds
market { id, question, state }
}
}
}리더보드(볼륨별)
query GetTopTraders($first: Int!) {
users(orderBy: totalVolume, orderDirection: desc, first: $first, where: { totalTrades_gt: 0 }) {
id, totalVolume, realizedPnL, totalTrades, totalProfitableSells
}
}다중 결과 시장 확보
query GetMultiOutcomeMarket($id: ID!) {
multiOutcomeMarket(id: $id) {
id, question, optionCount
options(orderBy: optionIndex) {
id, optionLabel, state, amm, yesTokenId, noTokenId, totalVolume
}
}
}인덱싱된 이벤트
그래프 하위 그래프는 다음 온체인 이벤트를 수신합니다.
| Contract | Event | Handler | Entity |
|---|---|---|---|
| DCPPFactory | DCPPFactory | DCPPFactory | Market |
| DCPPFactory | DCPPFactory | DCPPFactory | Market |
| MultiOutcomeMarketFactory | MultiOutcomeMarketFactory | MultiOutcomeMarketFactory | MultiOutcomeMarket |
| MultiOutcomeMarketFactory | MultiOutcomeMarketFactory | MultiOutcomeMarketFactory | Market |
| BinaryCPMM(템플릿) | BinaryCPMM | BinaryCPMM | 거래, 사용자, UserPosition |
| BinaryCPMM(템플릿) | BinaryCPMM | BinaryCPMM | 거래, 사용자, UserPosition |
| BinaryCPMM(템플릿) | BinaryCPMM | BinaryCPMM | 거래, 사용자, UserPosition |
계약 주소(BSC 테스트넷)
| Contract | Address |
|---|---|
| FactoryRegistry | FactoryRegistry |
| DCPPFactory | DCPPFactory |
| MultiOutcomeMarketFactory | MultiOutcomeMarketFactory |
| ConditionalTokens (CTF) | ConditionalTokens |
| 담보 토큰(USDC) | USDC |
속도 제한
사용자 ID / 관리자 ID / IP별로 분류된 슬라이딩 1분 창을 갖춘 Redis 기반 토큰 버킷 전략입니다.
| 엔드포인트 카테고리 | Limit | Identifier |
|---|---|---|
| 인증 임시값 | 100/min | IP |
| 인증 로그인 | 5/min | IP |
| 지갑 정보 | 100/min | User |
| TX에 서명하세요 | 20/min | User |
| Withdraw | 3/min | User |
| 바인드 초대 | 60/min | User |
| 주문장 읽기 | 120/min | IP |
| 주문서 작성 | 30/min | User |
| 관리자 인증 | 30/min | IP |
| 관리 작전 | 120/min | Admin |
| 관리자 로그인 | 30/min | Admin |
초과하면 429 Too Many Requests + Retry-After 헤더를 반환합니다.