트위터 API 초과, 헤더 3개로 429 재시도하는 법
트위터 API 초과는 HTTP 429예요. 15분 호출 한도와 월 300만 읽기 캡을 가르고, x-rate-limit-reset을 읽어 Python·Node로 재시도하는 법을 정리했어요.
개발자 호출에서 트위터 API 초과는 HTTP 429예요. X는 같은 429를 15분 호출 한도와 월간 사용량 캡 두 원인에 써요. x-rate-limit-reset(Unix 시각)과 Retry-After를 읽고, 리셋까지 기다린 뒤 지수 백오프로 재시도하면 돼요.
앱·웹에 "API 사용 제한 초과"만 보이면 소비자 읽기 캡이에요. 로그아웃·VPN으로는 안 풀려요. 소비자 쪽은 FAQ에 적어 두었어요. HTTP 429에 code: 88이나 type URI가 오면 개발자 호출이고, 이 글은 개발자 429를 다뤄요. 예제는 Python 3.11+ 또는 Node 20+이고, 인증은 X API v2 Bearer예요. 예전 Twitter API v2가 지금의 X API예요.
시작 전에 준비할 트위터 개발자 계정과 토큰
따라하기 전에 이것만 맞춰 두세요.
- developer.x.com에서 트위터 개발자 계정과 앱을 만들어요. 신규는 구독이 아니라 pay-per-use 크레딧이에요. (X API About)
- Bearer는 per-app이고, OAuth 사용자 토큰은 per-user예요. 한도가 토큰 종류별로 갈려요.
- 런타임은 Python 3.11+(
httpx) 또는 Node 20+(fetch)예요. curl로 헤더만 먼저 봐도 돼요. - HTTP 상태코드랑 Unix timestamp는 이미 안다고 보고 갈게요.
1단계. 트위터 API 제한은 엔드포인트마다 다른가요?
트위터 API 제한은 엔드포인트마다 다르고, 표기가 없으면 15분 창이에요. per-app(Bearer)과 per-user(OAuth)도 따로 세요.
한 경로가 429여도 전체가 죽지 않아요. 트위터 API 제한은 엔드포인트별 버킷이라, 검색이 막혀도 트윗 조회는 살아 있을 수 있어요. "전체 API 일시 차단"은 공식 동작이 아니에요. (X API Rate Limits)
숫자는 콘솔과 어긋날 수 있어요. 헤더가 진실이에요. 아래는 자주 터지는 읽기·쓰기만 모았어요. 숫자는 2026-09-02 기준 공식 문서예요.
| 엔드포인트 | app / 15분 | user / 15분 | 비고 |
|---|---|---|---|
GET /2/tweets/:id | 450 | 900 | |
GET /2/tweets/search/recent | 450 | 300 | 기본 10, 최대 100건, 쿼리 512자 |
GET /2/users/:id/tweets | 10,000 | 900 | |
GET /2/users/:id/followers | 300 | 300 | following도 동일 |
GET /2/users/by/username/:username | 300 | 900 | |
POST /2/tweets | 10,000 / 24h | 100 / 15분 | 창이 다름 |
GET /2/tweets/search/all | 1/초 + 300/15분 | 1/초 | full-archive |
출처: X API Rate Limits. 한도를 보는 헤더는 세 개예요.
| 헤더 | 의미 |
|---|---|
x-rate-limit-limit | 이 창의 최대 호출 |
x-rate-limit-remaining | 남은 호출 |
x-rate-limit-reset | 리셋 Unix 시각. 남은 초가 아님 |
import os
from datetime import datetime, timedelta, timezone
import httpx
KST = timezone(timedelta(hours=9))
r = httpx.get(
"https://api.x.com/2/tweets/20",
headers={"Authorization": f"Bearer {os.environ['X_BEARER_TOKEN']}"},
timeout=20,
)
reset = r.headers.get("x-rate-limit-reset")
print("limit ", r.headers.get("x-rate-limit-limit"))
print("remaining", r.headers.get("x-rate-limit-remaining"))
print("reset ", reset)
if reset:
print("reset KST", datetime.fromtimestamp(int(reset), KST).isoformat())
reset은 초가 아니라 Unix timestamp예요. 호출 한도를 앱 전역 락으로 묶지 마세요. 트위터 API 제한은 경로마다 창이 따로 돌아요. 다른 플랫폼 한도는 스레드 API 키워드 검색 7일 500회, 인스타그램 Graph API 시간당 200회를 보세요.
2단계. HTTP 429가 트위터 API에서 뜨는 이유는요?
HTTP 429는 15분 호출 한도이거나 월간 사용량 캡이에요. X는 둘 다 429로 내려요.
MDN의 429 Too Many Requests는 정해진 시간 안에 요청이 너무 많다는 뜻이고, Retry-After가 붙을 수 있어요. (RFC 6585 §4) X 공식 문서는 같은 429를 "Rate limit or usage cap exceeded"로 묶고, type만 rate-limit-exceeded와 usage-capped로 갈라요. (X response codes)
type을 안 읽으면 15분 sleep이 월간 캡을 못 풀어요. 바디 포맷은 세대가 두 개예요. 서로 충돌하는 스펙이 아니라 표기가 다른 거예요.
레거시:
{
"errors": [
{ "code": 88, "message": "Rate limit exceeded" }
]
}
현대:
{
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded",
"type": "https://api.twitter.com/2/problems/rate-limit-exceeded"
}
캡·크레딧이 바닥이면 type 끝이 usage-capped로 바뀌어요. 같은 429여도 다음에 할 일이 달라요. 200 응답의 errors[](멀티 ID 중 일부 없음)는 429가 아니니 재시도 루프에 넣지 마세요. (X response codes)
3단계. Rate limit exceeded가 뜨면 어떻게 하나요?
Rate limit exceeded면 x-rate-limit-reset Unix 시각까지 기다리고, Retry-After가 있으면 그것도 읽은 뒤 지수 백오프로 재시도해요.
공식 복구 순서는 이래요. (X API Rate Limits, X response codes)
type이 rate-limit인지 usage-capped인지 먼저 봐요. 후자면 sleep으로 안 풀려요.x-rate-limit-reset을 읽어요. 초가 아니라 Unix timestamp예요.Retry-After가 있으면 둘 중 더 늦은 시각까지 기다려요.- jitter 있는 지수 백오프로 재시도하고, 최대 5회예요.
- 429와 5xx에만 backoff해요. 다른 4xx는 재시도하지 마세요.
이 오류를 앱 전역 락으로 묶으면 검색이 막힌 김에 트윗 조회까지 멈춰요. 경로별로 기다리세요. Tweepy wait_on_rate_limit=True나 twitter-api-v2의 같은 옵션은 헤더를 직접 안 읽고 라이브러리에 맡기는 길이에요. 파이썬 수집 패턴은 파이썬으로 소셜 데이터 가져오기에 모아 두었어요.
import os
import random
import time
from datetime import datetime, timedelta, timezone
import httpx
BEARER = os.environ["X_BEARER_TOKEN"]
KST = timezone(timedelta(hours=9))
MAX_RETRIES = 5
def wait_until_reset(response: httpx.Response) -> None:
# reset is a Unix timestamp, not remaining seconds
reset = response.headers.get("x-rate-limit-reset")
retry_after = response.headers.get("Retry-After")
now = time.time()
wake = now
if reset:
wake = max(wake, float(reset))
if retry_after:
wake = max(wake, now + float(retry_after))
delay = max(0.0, wake - now) + random.uniform(0.2, 1.5)
print(f"sleep {delay:.1f}s until {datetime.fromtimestamp(wake, KST)}")
time.sleep(delay)
def get_tweet(tweet_id: str) -> dict:
url = f"https://api.x.com/2/tweets/{tweet_id}"
headers = {"Authorization": f"Bearer {BEARER}"}
with httpx.Client(timeout=20) as client:
for attempt in range(1, MAX_RETRIES + 1):
r = client.get(url, headers=headers)
print(
"limit=",
r.headers.get("x-rate-limit-limit"),
"remaining=",
r.headers.get("x-rate-limit-remaining"),
"reset=",
r.headers.get("x-rate-limit-reset"),
)
if r.status_code == 429:
try:
payload = r.json() if r.content else {}
except ValueError:
payload = {}
kind = str(payload.get("type") or "")
if "usage-capped" in kind:
raise RuntimeError("월간 캡/크레딧 — sleep으로 안 풀려요")
if attempt == MAX_RETRIES:
r.raise_for_status()
wait_until_reset(r)
continue
if r.status_code >= 500:
if attempt == MAX_RETRIES:
r.raise_for_status()
time.sleep((2**attempt) + random.uniform(0.1, 0.8))
continue
if r.status_code >= 400:
r.raise_for_status()
return r.json()
raise RuntimeError("retries exhausted")
if __name__ == "__main__":
print(get_tweet("20"))
Node는 fetch로 같은 순서예요. 헤더 이름이 달라도 Headers.get은 대소문자를 가리지 않아요.
const BEARER = process.env.X_BEARER_TOKEN;
const MAX_RETRIES = 5;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function waitUntilReset(res) {
const reset = res.headers.get("x-rate-limit-reset");
const retryAfter = res.headers.get("retry-after");
const now = Date.now() / 1000;
let wake = now;
if (reset) wake = Math.max(wake, Number(reset));
if (retryAfter) wake = Math.max(wake, now + Number(retryAfter));
const delayMs = Math.max(0, wake - now) * 1000 + Math.random() * 1300;
console.log(`sleep ${(delayMs / 1000).toFixed(1)}s`);
await sleep(delayMs);
}
async function getTweet(tweetId) {
const url = `https://api.x.com/2/tweets/${tweetId}`;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${BEARER}` },
});
console.log({
limit: res.headers.get("x-rate-limit-limit"),
remaining: res.headers.get("x-rate-limit-remaining"),
reset: res.headers.get("x-rate-limit-reset"),
});
if (res.status === 429) {
const body = await res.json().catch(() => ({}));
const type = String(body.type ?? "");
if (type.includes("usage-capped")) {
throw new Error("월간 캡/크레딧 — sleep으로 안 풀려요");
}
if (attempt === MAX_RETRIES) throw new Error("429 retries exhausted");
await waitUntilReset(res);
continue;
}
if (res.status >= 500) {
if (attempt === MAX_RETRIES) throw new Error(`HTTP ${res.status}`);
await sleep(2 ** attempt * 1000 + Math.random() * 800);
continue;
}
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return res.json();
}
}
getTweet("20").then(console.log).catch(console.error);
로그아웃, VPN, IP 변경, 앱 재설치는 개발자 429 해결책이 아니에요.
4단계. X API 무료 플랜 한도는 얼마인가요?
2026년 신규 X API에는 무료 구독 플랜이 없어요. 크레딧을 사서 쓰는 pay-per-use이고, 월 300만 Post read가 캡이에요.
신규 가입 화면에도 X API 무료 티어는 없고, 공식 문구는 "No subscriptions — pay only for what you use"예요. (X API Pricing) "하루 몇백 건 무료"는 구 티어 서술이에요.
X API 가격은 세 축이 따로 돌아요. 429여도 이미 가져온 데이터 과금은 남을 수 있고, 한도 안이어도 월 캡·크레딧 0에 막혀요. (Rate limits vs. billing)
| 축 | 무엇이 막히나 | 신호 | 과금과의 관계 |
|---|---|---|---|
| 15분/24h 요청 창 | 호출 빈도 | 429 rate-limit-exceeded, x-rate-limit-* | 한도에 걸려도 추가 과금 없음. 한도 안에서도 읽기 비용은 남 |
| 월간 Post read 캡 (tweet cap) | 월 과금 주기 안 읽기량 | 429 usage-capped, 크레딧 0 / Spending limit | 공식 300만/cycle. 이상은 Enterprise. 외부 글의 200만은 각주 |
| 크레딧/과금 | 잔액 | 요청 차단, Auto-recharge 5분에 1회 | Posts $0.005, User $0.010, Post Create $0.015, URL 포함 작성 $0.200, Owned Reads $0.001 |
2026-09-02 기준 공식 단가예요. 가격은 바뀔 수 있다고 문서가 못을 박아 두었어요. (X API Pricing)
2023년 TechCrunch는 Pro를 월 $5,000으로 보도했고, 2026년 3월 Postproxy는 레거시 Basic $200 / Pro $5,000이 기존 구독자에게만 남는다고 적었어요. (TechCrunch, Postproxy) 그 월액을 지금 가격처럼 적지 마세요.
월간 읽기는 Bearer로 GET https://api.x.com/2/usage/tweets를 보면 돼요. 이 엔드포인트 자체는 app 50/15분이에요. 유튜브는 하루 10,000 유닛 모델이라 유튜브 API 할당량과 창이 달라요.
5단계. 공식 X API 한도에 안 걸리는 트위터 읽기 경로는요?
공식 X 호출 한도를 안 쓰는 읽기 경로가 있어요. SocialCrawl은 x-api-key로 트위터 프로필·트윗을 가져오고, 한도는 키당 분당 600회예요.
우회 광고가 아니에요. 공식 X 버킷을 안 쓰는 읽기 API예요. 인증은 x-api-key고, 프로필 조회는 1cr예요. (트위터 문서)
curl "https://www.socialcrawl.dev/v1/twitter/profile?handle=elonmusk" \
-H "x-api-key: YOUR_API_KEY"
Twitter/X GET 엔드포인트는 15개예요. 분당 600회, 동시 50개 — 둘 다 429이고 크레딧은 안 깎여요. 볼륨 한도는 크레딧(402 INSUFFICIENT_CREDITS)이고, 한도를 플랜으로 팔지 않아요. 플랫폼은 55개고 엔드포인트는 449개예요. (레이트 리밋)
프로필·트윗 경로는 트위터/X 엔드포인트 문서에 있고, 분당 600은 레이트 리밋에 적혀 있어요. 같은 슬롯의 다른 벤더는 소셜 미디어 크롤링 API 10곳 비교를 보세요.
트위터 API 사용 제한 초과가 remaining=0이 아닌데도 뜨는 이유는요?
트위터 API 사용 제한 초과는 remaining이 0일 때만 오는 게 아니에요.
- 첫 요청부터 429: 공유 Bearer를 쓰는 다른 워커가 같은 앱 버킷을 이미 다 썼을 수 있어요. remaining은 워커 합으로 보세요. 트위터 API 초과가 내 루프 밖에서 먼저 난 거예요.
- remaining>0인데 429: per-app과 per-user를 섞었거나, usage-capped를 rate-limit으로 착각한 경우예요.
typeURI를 읽어요. - 한 엔드포인트 429 = 전체 다운: 버킷은 엔드포인트마다 따로예요. 다른 경로는 살아 있어요.
- 200 +
errors[]를 429로 처리: 재시도 루프에 넣지 마세요. - 앱 UI "API 사용 제한 초과": 트위터 API 사용 제한 초과 카피지만 개발자 429가 아니에요. 소비자 쪽은 FAQ에 적어 두었어요.
한도에 안 걸리려면 다음에 뭘 할까요?
GET /2/usage/tweets로 월간 읽기를 주기적으로 보세요. 공식 창에 반복해서 막히면 트위터 플랫폼과 트위터/X 엔드포인트 문서의 프로필·트윗 경로를 익스플로러에서 확인해 보세요. 유튜브 할당량은 유튜브 API 하루 10,000 유닛에서 이어 가면 돼요.
자주 묻는 질문
트위터 API 초과는 시간 지나면 풀리나요?
개발자 429(rate-limit)는 x-rate-limit-reset 시각에 풀려요. 창은 보통 15분 또는 24시간이에요. usage-capped는 월 주기·크레딧 충전 전엔 안 풀려요. 앱 UI에는 헤더가 없어서, 풀리는 시각을 직접 확인할 수 없어요.
앱에 뜨는 'API 사용 제한 초과'랑 개발자 API 429는 같은 건가요?
아니요. 앱/웹 카피는 소비자 읽기 캡이에요. 개발자 429는 HTTP 상태와 code: 88 또는 type URI예요. 로그아웃·VPN은 개발자 429 해결책이 아니에요.
HTTP 429 Too Many Requests는 어떻게 처리하나요?
type을 확인한 뒤 reset과 Retry-After 중 더 늦은 시각까지 기다리고, jitter 백오프로 재시도해요. 5xx만 같이 재시도하세요. 본문 3단계 코드를 그대로 쓰면 돼요.
X API 무료 플랜 호출 한도는 얼마인가요?
2026년 신규에는 무료 구독이 없어요. pay-per-use이고, 월 300만 Post read가 캡이에요. "하루 몇백 건 무료"는 구 티어 서술이라 따라 적지 마세요.
Rate limit exceeded 헤더는 어디서 보나요?
응답 헤더 x-rate-limit-limit / remaining / reset이에요. reset은 Unix timestamp예요. 429에만 Retry-After가 붙는 경우도 있어요.
트위터 API 한도를 늘리려면 유료 플랜을 써야 하나요?
신규는 플랜 업그레이드가 아니라 크레딧과 Enterprise예요. 레거시 Basic/Pro는 기존 구독자 이야기예요. 한도를 돈으로 사는 일과 읽기 단가(Post $0.005)를 섞지 마세요.
공식 API 한도에 안 걸리는 트위터 데이터 받는 법이 있나요?
공식 X 한도를 안 쓰는 읽기 API가 있어요. SocialCrawl 예는 GET /v1/twitter/profile이고, 키당 분당 600회예요. 공식 버킷을 안 쓰는 경로이지, 한도를 뚫는 우회가 아니에요.
공식 한도 표는 X Rate Limits에 있고, 트위터 읽기 경로는 트위터/X 엔드포인트 문서를 보세요. 유튜브 할당량은 유튜브 API 2026에서 이어 가면 돼요.
함께 읽으면 좋은 글
유튜브 쇼츠 해시태그, 452개를 세어봤어요
2026년 8월 31일 유튜브 쇼츠 해시태그 452개 실측. 중앙값 5개, 조회수와 스피어만 ρ=−0.27. 0개는 17.0%, 15개 이상은 7.5%예요.
Claude MCP 서버 4개, 용도별로 골랐어요
Claude MCP로 지금 붙일 서버 4개를 용도별로 골랐어요. Code와 Desktop 설정 차이, 복사해 넣는 mcp.json, 소셜 데이터 MCP까지요.
한국 틱톡 크리에이터 100명, 주 8회 넘게 올리면 조회수 효율이 떨어져요
한국 틱톡 크리에이터 100명의 실측 데이터로 포스팅 빈도와 조회수 관계를 확인했어요. 주 8회 넘게 올리면 게시물당 조회수 효율이 팔로워 대비 가장 낮았어요.
