서버
레이트리밋과 에러 코드
오류는 모두 같은 형식으로 돌려줘요. 코드로 분기하고, 메시지는 사람이 읽을 수 있게 보여 주세요.
오류 형식
{
"error": {
"code": "rate_limited",
"message": "요청이 너무 많습니다. 잠시 후 다시 시도하세요",
"details": {
"retryAfterSeconds": 120
}
}
}code는 아래 표의 값 중 하나예요. message는 한국어 안내 문구이고 바뀔 수 있으니 분기에 쓰지 마세요. details는 선택이며, validation_failed일 때는 어떤 필드가 잘못됐는지 담겨요.
에러 코드
| code | HTTP | 의미 |
|---|---|---|
bad_request | 400 | 요청 형식이 잘못됐어요. |
validation_failed | 422 | 필드 값이 올바르지 않아요. details를 확인하세요. |
unauthenticated | 401 | 키나 토큰이 없거나 맞지 않아요. |
token_expired | 401 | 세션 토큰이 만료됐어요. 다시 부팅하세요. |
forbidden | 403 | 이 작업을 할 권한이 없어요. |
not_found | 404 | 대상을 찾을 수 없어요. |
conflict | 409 | 대상의 현재 상태와 충돌해요. 최신 상태를 다시 읽고 시도하세요. |
rate_limited | 429 | 요청이 너무 많아요. Retry-After 뒤에 다시 시도하세요. |
otp_invalid | 400 | 인증 코드가 틀렸어요. |
otp_expired | 400 | 인증 코드가 만료됐어요. 새 코드를 받으세요. |
otp_used | 400 | 이미 사용한 인증 코드예요. |
internal | 500 | 엔트로피 쪽 오류예요. 잠시 후 다시 시도하세요. |
레이트리밋
| 대상 | 한도 |
|---|---|
| 인증 코드 보내기 — 목적지별 | 15분에 5번 |
| 인증 코드 보내기 — IP별 | 1시간에 20번 |
| 인증 코드 확인 | 코드 하나당 5번 |
한도를 넘으면 429와 함께 Retry-After 헤더(초)를 보내요. 가능한 경우 details.retryAfterSeconds에도 같은 값을 담아요. 그 시간이 지나기 전에는 다시 보내지 마세요.
재시도
429와5xx, 네트워크 오류만 재시도하세요.4xx는 요청을 고쳐야 해요.- 간격을 늘려 가며(예: 1초, 2초, 4초…) 무작위 지연을 더해 재시도하세요.
- 메시지를 보낼 때는
clientMsgId를 넣으세요. 같은 값으로 다시 보내도 메시지는 한 번만 저장돼요.
const clientMsgId = crypto.randomUUID(); // 재시도해도 같은 값을 써요
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(`${base}/v1/messenger/conversations/${id}/messages`, {
method: 'POST',
headers: { Authorization: `Bearer ${sessionToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ body: text, clientMsgId }),
});
if (res.ok) break;
if (res.status !== 429 && res.status < 500) throw new Error(await res.text());
const wait = Number(res.headers.get('retry-after')) * 1000 || 2 ** attempt * 1000;
await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
}