가입하면 프리미엄 플랜 할인할인 받기

블로그/developers·2026년 8월 31일·5분·eroq 팀 작성

AI 동영상 API 요청 한도 정리: 상한, 배수, 재시도

eroq의 키별 분당 상한, 플랜 배수와 멤버별 배분이 한도를 바꾸는 방식, 429를 받았을 때 제대로 백오프하는 법을 정리했어요.


요청 한도(레이트 리밋)는 API에서 무시하고 지내는 부분이에요. 어느 화요일 오후, 배치 작업과 제품 출시가 같은 1분 안에 겹쳐 모든 요청이 429를 돌려주기 전까지는요. 그제야 재시도 로직이 루프 안의 1초짜리 setTimeout이라는 걸 알게 되죠. 그건 백오프가 아니에요. 같은 우르르 몰려들기를 조금 느린 템포로 반복하는 것뿐이에요.

이 글에서는 eroq의 한도가 어떻게 계산되는지 숫자가 적용되는 순서대로 설명하고, 한도에 걸렸을 때 얌전하게 동작하는 클라이언트를 작성하는 법을 다뤄요.

기본 상한: 키별, 분당

숫자는 세 개이고, API 키별로 60초 슬라이딩 윈도우 안에서 집계돼요.

  • 채팅: 분당 60회. 스트리밍 여부와 관계없이 POST /v1/chat/completions에 적용돼요. SSE 스트림은 토큰마다가 아니라 여는 순간 한 번만 집계돼요.
  • 이미지: 분당 20회. 네 장을 한 번에 요청하는 호출도 요청 한 번으로 집계돼요.
  • 동영상과 음성 합성: 각각 분당 6회.

분당 동영상 요청 6회는 적어 보이지만, 동영상 요청이 뭔지 떠올려 보면 얘기가 달라요. 렌더가 아니라 제출이에요. POST /v1/videos/generations는 작업 id를 즉시 돌려주고 클립은 몇 분 뒤에 도착하니, 분당 6회 제출은 분당 새 렌더 6개라는 뜻이고, 이건 상당한 제작량이에요. 엔드포인트 전체 레퍼런스는 /docs/video에 있어요.

단위에 주의하세요. 계정별이 아니라 키별이에요. 키가 두 개면 독립된 윈도우도 두 개예요. 파이프라인마다 키를 하나씩 발급하라는 조언이 바로 이 원리에서 나와요. 스테이징에서 폭주한 루프는 스테이징만 막아요.

플랜 배수

활성 플랜은 워크스페이스의 모든 키에서 이 기본 상한 하나하나에 배수를 곱해요.

  • 플랜 없음, Hobby: 1×
  • Creator: 2×
  • Studio: 4×
  • Team: 6×
  • Agency: 8×

그래서 동영상 제출은 분당 6회에서 Studio는 24회, Agency는 48회로 늘고, 채팅은 60회에서 각각 240회, 480회가 돼요. 키 개수도 함께 늘어나니(Hobby 10개, Creator 20개, Studio 50개, Team 100개, Agency 200개), 큰 플랜의 실제 상한은 배수에 운영할 의향이 있는 키 개수를 곱한 값이에요. 표는 /pricing에 있어요.

그 위에 얹히는 멤버별 배분

워크스페이스 안에서는 멤버의 역할에 따라 그 멤버의 키가 받는 몫이 줄어요. 개발자나 관리자는 플랜 한도를 전부 받고, 크리에이터는 절반에서 시작해요. 스튜디오에서 하는 대화형 작업이 같은 플랜을 쓰는 프로덕션 파이프라인을 굶기면 안 된다는 생각에서예요.

관리자는 /dashboard/workspace의 ‘멤버’ 탭에서 멤버마다 이 몫을 퍼센트로 더 줄일 수 있어요. 이 설정은 역할의 몫을 줄일 수만 있고, 역할이 허용하는 것 이상으로 올릴 수는 없어요.

계산은 한 방향으로만 곱해지니 예측하기 쉬워요.

effective limit = base cap × plan multiplier × member share

Studio 플랜에서 크리에이터 역할을 맡은 팀원이 동영상 엔드포인트를 호출하면 이래요.

6 × 4 × 0.5 = 12 video submissions per minute, per key

그 멤버를 25%로 줄이면 6회가 돼요. 여기서 일어나지 않는 일이 두 가지 있는데, 둘 다 의도된 거예요. 줄어든 몫은 무료 읽기 전용 엔드포인트를 절대 막지 않아요. 작업 목록 조회나 계정 잔액 확인은 그대로 돼요. 그리고 생성 권한이 아예 없는 멤버는 정체불명의 429가 줄줄이 뜨는 대신, 크레딧이 빠져나가려는 순간 명확한 권한 오류로 거절돼요.

429는 실제로 이렇게 생겼어요

상태 코드, 초 단위의 Retry-After 헤더, 그리고 적용된 범위와 한도를 알려 주는 본문이 와요.

{
  "error": {
    "message": "Rate limit reached for video (24/min per key). Retry in 37s.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded"
  }
}

쓸모 있는 건 헤더예요. 윈도우가 계속 미끄러지듯 움직이기 때문에 Retry-After는 고정된 쿨다운이 아니에요. 현재 윈도우에서 가장 오래된 요청이 빠져나가 자리가 하나 날 때까지 남은 초예요. 정확히 그만큼 기다리는 게 맞고, 1초 기다렸다 다시 시도하는 건 틀려요.

제대로 백오프하기

우선순위 순서대로 규칙 네 가지예요.

1. Retry-After가 있으면 따르세요. 실제 윈도우를 기준으로 계산된 값이라, 직접 고안한 어떤 휴리스틱보다 나아요.

2. 없으면 지터를 넣은 지수 백오프를 쓰세요. 두 배씩 늘리기만 하면 모든 클라이언트가 같은 재시도 순간으로 다시 맞춰져요. 0과 현재 상한 사이의 무작위 지연인 풀 지터(full jitter)를 쓰면 요청이 흩어져요.

3. 일시적이지 않은 오류는 재시도하지 마세요. 402 insufficient_credits, 403 plan_required, 403 role_forbidden, 그리고 content_blocked 거절은 30초 뒤에도 정확히 같은 답을 돌려줘요. 그대로 사용자에게 보여 주세요. 503 model_unavailable은 그 엔진을 지금 내 계정에 제공할 수 없다는 뜻이에요. 재시도 루프보다 /models에서 다른 엔진을 고르는 게 더 나은 대응이에요.

4. 벽에 대고 재시도하지 말고 동시성을 제한하세요. 실효 한도와 같거나 살짝 낮게 잡은 세마포어는 요청 한도를 오류 처리 경로에서 스케줄링 문제로 바꿔 줘요. 원래 있어야 할 자리로요.

전체 코드예요. 그대로 붙여 넣을 만큼 짧아요.

const sleep = ms => new Promise(r => setTimeout(r, ms))

async function submitVideo(body, { attempts = 5 } = {}) {
  let delay = 1000
  for (let attempt = 1; attempt <= attempts; attempt++) {
    const res = await fetch('https://eroq.ai/v1/videos/generations', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    })

    if (res.ok) return res.json()          // 202 with a job id
    if (res.status !== 429) {              // 402/403/503 are not transient
      throw new Error(`${res.status} ${(await res.json()).error?.code}`)
    }

    const header = Number(res.headers.get('retry-after'))
    const wait = Number.isFinite(header) && header > 0
      ? header * 1000
      : Math.random() * delay              // full jitter
    await sleep(wait)
    delay = Math.min(delay * 2, 30_000)
  }
  throw new Error('rate limited after all attempts')
}

사용자가 직접 마주하는 경로라면 다섯 번 시도가 적당한 상한이에요. 배치 워커라면 재시도 루프를 아예 빼고, 제출 속도가 고정된 큐가 속도를 조절하게 하세요. 한도에 걸리는 일이 훨씬 줄고, 걸리더라도 큐가 자연스러운 대기 장소가 돼요.

실제로 먼저 걸리는 한도

실제로 대부분의 팀은 동영상 상한에 닿지도 않아요. 분당 6회면 시간당 360회이고, 요청 한도가 불평하기 훨씬 전에 지갑이 먼저 비어요. 동영상 작업에서 진짜 제약은 크레딧과 렌더에 걸리는 실제 시간이에요. 그래서 이 문제에서는 이 글보다 예산 관련 글과 /pricing 페이지가 더 중요해요.

채팅은 정반대예요. 무료나 Hobby 플랜의 분당 60회는 컴패니언 앱이나 롤플레이 서비스가 바쁜 저녁을 맞는 순간 실제로 닿을 수 있는 숫자이고, 사용자의 대화 한 턴이 곧 요청 하나예요. 채팅 위에 서비스를 만든다면 배수를 염두에 두고 계획하고, 화면마다 키를 나눠 부하를 분산하고, 재시도 경로를 설계하기 전에 SSE 스트리밍 가이드를 읽어 보세요. 응답 도중에 끊긴 스트림은 시작조차 못 한 요청과 다르게 처리해야 하거든요.

이미지는 그 중간이에요. 분당 20회는 대화형으로 쓰기엔 넉넉하고 대량 카탈로그 작업에는 빠듯해요. 병렬 요청을 한꺼번에 퍼뜨리기보다 직접 제어하는 큐를 써야 하는 또 하나의 이유예요.

자주 묻는 질문

eroq의 요청 한도는 키별인가요, 계정별인가요?

키별이에요. API 키마다 1분짜리 슬라이딩 윈도우가 따로 있어서, 환경이나 파이프라인마다 키를 하나씩 발급하면 폭주한 루프가 워크스페이스의 다른 모든 작업을 막는 일을 피할 수 있어요.

플랜을 업그레이드하면 요청 한도가 올라가나요?

네. 문서에 적힌 상한이 기본값이고, 플랜이 모든 키에서 여기에 배수를 곱해요. Creator 2×, Studio 4×, Team 6×, Agency 8×예요. 키 개수도 플랜에 따라 늘어나요.

동영상 엔드포인트에서 429를 받으면 어떻게 해야 하나요?

Retry-After 헤더를 읽고 정확히 그만큼 기다린 다음 한 번 재시도하세요. 헤더가 없으면 풀 지터를 넣은 지수 백오프를 쓰고, 다음 배치가 같은 벽에 부딪히지 않도록 동시성을 제한하세요.

/docs에서 엔드포인트 레퍼런스를 읽고, 내 플랜의 배수는 /faq에서 확인하세요.

태그rate-limitsapiretriesvideo-api

이 글에 나온 모델로 직접 만들어 보세요 — 무료 크레딧 50개로 시작하거나, 모든 엔진과 가격도 살펴보세요.