# 공통 규칙

## 요청과 응답

- Base URL: `https://api.furstock.trade`
- 요청 바디는 `application/json`이에요. 이미지 업로드(상장 신청, 갤러리)만 `multipart/form-data`예요.
- 응답은 항상 JSON이고, 필드명은 camelCase예요. 삭제 API 일부는 바디 없이 `204`를 돌려줘요.
- **금액·가격**은 전부 정수 "원"이에요. 퍼스톡 가상 화폐이고, 소수점은 없어요.
- **시각**은 ISO 8601 UTC 문자열이에요(예. `2026-09-24T03:00:00.000Z`). 예외로 시세 스냅샷·체결의 `ts`는 epoch 밀리초, 캔들의 `time`은 epoch 초예요.
- **종목 식별자(`symbol`)는 캐릭터 이름 그 자체**예요. 영문 티커가 아니라 한글일 수도 있어요(예. `보팔래빗`). 경로에 넣을 땐 URL 인코딩하세요(`/v1/stocks/%EB%B3%B4%ED%8C%94%EB%9E%98%EB%B9%97`). 1~20자이고 유니코드 문자·숫자와 기호 `-` `.` `_` `~` `(` `)`로만 이루어져요(공백·`/`·`?` 등은 없어요). `(` `)`는 인코딩하지 않아도 되지만, `encodeURIComponent`로 통째로 인코딩해도 괜찮아요.
- 모든 응답에 `X-Request-Id` 헤더가 있어요. 문의할 때 이 값을 알려주시면 빨리 찾을 수 있어요.

## 오류

오류 응답은 모두 같은 모양이에요.

```json
{ "error": "not_found", "message": "사람이 읽는 설명(없을 수도 있음)" }
```

| 상태 | 대표 `error`                                          | 의미                                                                |
| ---- | ----------------------------------------------------- | ------------------------------------------------------------------- |
| 400  | `invalid_request`                                     | 파라미터·바디 형식 오류. `issues`에 상세가 붙기도 해요              |
| 401  | `unauthorized`, `invalid_client`                      | 토큰 없음·만료·무효, 폐기된 키                                      |
| 403  | `forbidden`                                           | 권한 없음(예. 남의 종목에 뉴스 작성)                                |
| 403  | `session_required`                                    | API 키로는 할 수 없는 작업([인증](/developers/authentication) 참고) |
| 404  | `not_found`                                           | 대상 없음(없는 종목, 남의 주문 등)                                  |
| 409  | `not_pending`, `duplicate_symbol`, `too_many_keys` 등 | 현재 상태와 충돌                                                    |
| 429  | `rate_limited`                                        | 요청 한도 초과                                                      |
| 500  | `internal_error`                                      | 서버 오류. `reqId`와 함께 문의해 주세요                             |

**주문 거부는 오류가 아니에요.** 잔고 부족처럼 주문이 거부돼도 HTTP는 `200`이고, 응답 바디의 `status: "rejected"`와 `rejectReason`으로 알려줘요. [주문](/developers/orders)을 보세요.

## 요청 한도

| 대상                           | 한도                                          |
| ------------------------------ | --------------------------------------------- |
| API 키로 받은 토큰의 모든 요청 | 키당 **초당 10회**, 순간 최대 20회(토큰 버킷) |
| `POST /oauth2/token`           | client_id당 **분당 30회**                     |

한도를 넘으면 `429`와 `Retry-After`(초) 헤더가 와요. 그만큼 기다렸다 다시 보내세요. 정상 응답엔 `X-RateLimit-Limit`(초당 허용 횟수), `X-RateLimit-Remaining`(지금 남은 여유) 헤더가 붙어요.

> 여러 종목 시세를 자주 봐야 한다면 종목마다 폴링하지 말고 [실시간 시세](/developers/realtime) WebSocket을 쓰세요. WebSocket 수신은 요청 한도에 포함되지 않아요.

## 멱등성 (주문 중복 방지)

`POST /v1/orders`는 `clientToken`(UUID)을 필수로 받아요.

- 새 주문마다 **새 UUID**를 만드세요.
- 타임아웃이나 네트워크 오류로 결과를 못 받았다면 **같은 `clientToken`으로 다시 보내세요.** 서버가 이미 처리한 주문이면 새로 체결하지 않고 처음 결과를 그대로 돌려줘요.

## 페이지네이션

목록 API는 대부분 고정 상한만 있어요(예. 커뮤니티 최신 200개). 예외:

| API                               | 방식                                                                      |
| --------------------------------- | ------------------------------------------------------------------------- |
| `GET /v1/orders`                  | 커서. `limit`(최대 200) + `before`(이전 페이지 마지막 항목의 `createdAt`) |
| `GET /v1/stocks/{symbol}/gallery` | 페이지 번호. `page`(1부터), 응답의 `totalCount`, `pageSize`               |

## CORS

모든 origin에서 호출할 수 있어요. 쿠키 없이 `Authorization` 헤더만 쓰는 구조라서 그래요. 단, 브라우저 코드에 `client_secret`을 넣으면 그대로 노출된다는 점에 주의하세요.

## 시장 규칙

API로 하는 거래도 웹과 똑같은 규칙을 따라요.

| 규칙          | 값                                                                                       |
| ------------- | ---------------------------------------------------------------------------------------- |
| 가입 시 지급  | 300,000원 (입출금 없음)                                                                  |
| 운영 시간     | **24시간 연속**. 휴장 없음                                                               |
| 세션          | KST 00·03·06·09·12·15·18·21시마다 새 세션. 직전 종가가 새 시작가가 돼요                  |
| 가격 제한폭   | 세션 시작가 **±30%** (최저가 500원)                                                      |
| 서킷브레이커  | 세션 시작가 대비 **±15%** 움직이면 그 종목 **5분** 거래정지(방향별 세션당 1회)           |
| 수수료        | 체결 금액의 **0.15%** (매수·매도 모두)                                                   |
| 세금          | 매도 체결 금액의 **0.2%**                                                                |
| 체결 방식     | 실제 주문장이 아닌 **가격 곡선(본딩 커브)** 모델. 사는 만큼 오르고 파는 만큼 내려요      |
| 지정가        | 대기했다가 가격이 닿으면 **전량 체결**(부분체결 없음). 세션 정산 때 미체결분은 자동 취소 |
| 거래정지 종목 | `tradingHalted: true`인 종목은 주문이 거부돼요(`admin_halted`)                           |
| 관리종목·숨김 | `hidden: true`면 검색·순위에서 빠지지만 거래는 돼요(관리자 지정·상장주 숨김 둘 다)       |
