제미나이 무료 API, 쿼터 미터, 구조화 출력, 유료 전환 | 소프트 캡 코드는 어떻게 붙이나?
제미나이 무료 API 키 발급부터 @google/genai SDK 스모크 호출, RPM·TPM·RPD 한도 스냅샷, 구조화 JSON 출력, 소프트 캡 쿼터 미터, 429 백오프, 사이드 프로젝트 일일 용량 식, 청구 연동·Vertex·원화 부가세 시나리오까지 단계별 실습으로 정리한다. AI 스튜디오 Rate Limit·Pricing 출처와 개발자·API·LLM·사이드 프로젝트 운영 가이드.
제미나이(Gemini) 무료 API로 사이드 프로젝트를 붙일 때 먼저 끝낼 일은 네 가지입니다. AI 스튜디오에서 키 발급, 신 GenAI SDK로 한 줄 호출, 분당·일일 한도를 코드에 미러링, 429가 제품 약속을 깨기 전에 유료 전환 시점을 표로 고정. 카드 없이도 토큰 요금 0원으로 시작할 수 있지만, RPM·TPM·RPD 중 하나만 넘어도 요청이 거절됩니다. 한도는 키마다 따로가 아니라 프로젝트 단위로 잡히는 경우가 많고, 고정 숫자는 시점에 따라 바뀌므로 배포 전 AI Studio Rate Limit 화면을 최종 기준으로 둡니다.
키 발급·curl 스모크만 필요하면 첫 호출 튜토리얼을, 서버 RPD 게이트·스트림은 서버 실습을, 표 중심 손익은 유료 전환 손익을, 개념 개요는 무료 티어 가이드를 같이 보면 됩니다. 아래는 “SDK 설치부터 쿼터 미터·원화 전환 표”까지 터미널에 붙여 넣는 순서입니다.
오늘 실습 지도 | 무료 키에서 전환 표까지 8단계
한 단계가 통과해야 다음으로 갑니다. “설명만 읽고 끝”이 아니라, 각 단계마다 터미널 출력 또는 파일 결과물이 있어야 합니다.
단계
결과물
실패 시 증상
1. 키 발급
AI 스튜디오 API 키 + 프로젝트 ID 메모
권한 없음·키 유출
2. SDK 설치
@google/genai 패키지
구 패키지 혼용·import 오류
3. 스모크 호출
HTTP 200 + 짧은 텍스트
401·모델 ID 404
4. 한도 스냅샷
RPM·TPM·RPD 메모 파일
블로그 숫자와 대시보드 불일치
5. 구조화 JSON
스키마 고정 응답
파싱 실패·재호출 폭주
6. 쿼터 미터
일일·분당 소프트 캡
서버리스 인스턴스 간 공유 실패
7. 429 백오프
재시도 상한 코드
즉시 재시도로 한도 더 소진
8. 전환 표
무료 유지 / 청구 연동 / Vertex
환율·부가세 누락
무료 티어는 입력·출력 토큰 요금이 0원이지만, 요청 횟수와 분당 토큰으로 막힙니다. 공식 설명은 Rate limits와 Pricing에 있습니다. 글 안 숫자는 계산 연습용이며, 배포 직전 두 페이지로 덮어쓰세요.
실습 전제 | Node.js 18+, 구글 계정, 터미널. 키는 서버·로컬 스크립트에만 둡니다. 브라우저 번들·공개 저장소·슬랙 스크린샷에 키를 올리지 마세요. 모델 ID 예시(gemini-2.5-flash 등)는 시점에 따라 바뀌므로 AI 스튜디오 모델 목록에서 다시 고정합니다. 1인 개발자 사이드 프로젝트 기준이며, 조직 VPC·IAM이 필요하면 Vertex 경로를 별도로 봅니다.
1단계 | 무료 API 키 발급과 프로젝트 단위 한도
키 발급 자체는 짧습니다. 막히는 지점은 “키를 코드에 붙여 넣고 깃에 올리는” 실수와, “키를 여러 개 만들면 한도가 늘어난다”는 오해입니다.
프로젝트 ID를 메모한다. RPM·TPM·RPD는 키마다 따로가 아니라 프로젝트 단위로 합산되는 경우가 일반적이다.
키 문자열을 비밀번호처럼 취급한다. 이슈·슬랙·스크린샷에 올리지 않는다.
로컬은 .env.local, 배포는 호스트 시크릿(버셀 등)으로만 주입한다.
같은 프로젝트에 키를 10개 만들어도 일일 요청(RPD) 풀은 공유됩니다. 한도를 늘리려면 키 복제가 아니라 사용 티어 승급(청구 연동·누적 사용)이나 공식 한도 상향 경로를 봅니다. 점검 표는 사이드 프로젝트 체크리스트를 참고하세요.
환경 변수 준비 (.env.local, 깃 제외)
# .env.local — .gitignore 에 반드시 추가
GEMINI_API_KEY=여기에_발급받은_키
GEMINI_MODEL=gemini-2.5-flash
# 소프트 캡(실한도보다 낮게). 배포 전 Rate Limit 화면 숫자로 덮어쓰기
SOFT_RPD=1200
SOFT_RPM=10
# 1회 로드 (zsh/bash)
export $(grep -v '^#' .env.local | xargs)
echo "key length: ${#GEMINI_API_KEY}"
# 저장소에 키 문자열이 남았는지 확인 (출력되면 즉시 폐기·재발급)
git grep -nE 'AIza[0-9A-Za-z_-]{20,}' || echo "no key string in repo (good)"
키가 유출되면 AI 스튜디오에서 폐기하고 새로 만듭니다. 무료라도 남이 내 한도를 다 쓰면 내 서비스가 종일 429를 맞습니다. 배포 환경에는 대시보드 시크릿으로만 넣고, NEXT_PUBLIC_ 접두사 변수에는 절대 넣지 마세요.
제미나이 무료 API 키는 AI 스튜디오에서 발급하고 환경 변수로만 주입한다
2단계 | 신 GenAI SDK 설치와 첫 호출
예전 @google/generative-ai 예제가 아직 많이 돌아다닙니다. 신규 코드는 공식 문서가 안내하는 @google/genai 쪽을 기준으로 맞추는 편이 유지보수에 유리합니다. 패키지 이름·import 경로·클라이언트 생성 방식이 다르므로, 한 파일 안에서 구·신 SDK를 섞지 마세요.
아래는 최소 스모크입니다. 모델 ID는 환경 변수로 빼고, 응답 텍스트 길이만 확인하면 됩니다.
패키지 설치
mkdir gemini-free-lab && cd gemini-free-lab
npm init -y
npm install @google/genai dotenv
# Node 18+ 권장. type:module 을 package.json 에 켜 두면 ESM import 가 편합니다.
smoke.mjs — 무료 키로 한 줄 호출
import 'dotenv/config'
import { GoogleGenAI } from '@google/genai'
const apiKey = process.env.GEMINI_API_KEY
const model = process.env.GEMINI_MODEL || 'gemini-2.5-flash'
if (!apiKey) {
console.error('GEMINI_API_KEY missing')
process.exit(1)
}
const ai = new GoogleGenAI({ apiKey })
const res = await ai.models.generateContent({
model,
contents: '한 문장으로 인사만 해 주세요. 10자 이내.',
})
const text = res.text ?? JSON.stringify(res).slice(0, 200)
console.log('ok', { model, preview: String(text).slice(0, 80) })
// usageMetadata 가 있으면 토큰 추적용으로 로그
if (res.usageMetadata) console.log('usage', res.usageMetadata)
401·403이면 키·프로젝트 권한을, 404에 가까운 모델 오류면 모델 ID를 AI 스튜디오 목록과 대조합니다. 응답이 오면 usageMetadata(토큰 사용량)를 로그에 남기는 습관을 들이세요. 2주치만 모아도 유료 전환 손익 표의 입력이 됩니다. SDK 필드명은 버전에 따라 다를 수 있어, 한 번 console.log(Object.keys(res))로 확인하는 편이 안전합니다.
3단계 | 무료 티어 한도 스냅샷 (RPM·TPM·RPD)
블로그에 적힌 “분당 15회” 같은 숫자를 코드에 박지 마세요. 계정·모델·사용 티어·시점에 따라 다릅니다. 배포 전 할 일은 하나입니다. Rate Limit 화면을 열고, 쓸 모델 행의 RPM·TPM·RPD를 파일로 남깁니다.
약어
의미
사이드 프로젝트에서 자주 터지는 순간
RPM
분당 요청 수
프론트 연타·재시도 루프·배치 병렬
TPM
분당 토큰(입·출 합산 기준은 문서 확인)
긴 컨텍스트·대용량 요약·에이전트 다단 호출
RPD
일일 요청 수
공개 데모·크롤러·야간 배치 누적
공식 문서는 일일 쿼터가 태평양 시간(PT) 자정 기준으로 리셋된다고 안내하는 경우가 많습니다. 국내 저녁 피크와 리셋 시각이 어긋날 수 있으니, 배치 작업은 리셋 직후로 미루는 편이 안전합니다. 서머타임 구간은 오프셋이 바뀌므로 America/Los_Angeles zoneinfo를 쓰는 편이 낫습니다.
quota-snapshot.md 템플릿 (수동 캡처 후 커밋)
# gemini-quota-snapshot (배포 전 체크인)
- captured_at_kst: 2026-08-08 10:00
- project_id: your-gcp-project-id
- source: https://aistudio.google.com/rate-limit
- model: gemini-2.5-flash
- rpm: (화면 숫자)
- tpm: (화면 숫자)
- rpd: (화면 숫자)
- soft_rpm: floor(rpm * 0.7)
- soft_rpd: floor(rpd * 0.75)
- notes: 키 N개는 동일 프로젝트 합산 가정
# 운영 규칙
# 1) soft_* 만 코드에 넣는다 (실한도 직전까지 쓰지 않음)
# 2) 모델·티어 바뀌면 이 파일을 먼저 고친다
# 3) 2주마다 usageMetadata 합계를 아래에 붙인다
일일 용량 감 잡기 | RPD가 1,500이고 기능 1회가 업스트림 3호출(분류+요약+검증)이면, 순수 사용자 액션은 이론상 500회입니다. 소프트 캡 75%면 약 375회. “가입자 수”가 아니라 기능당 업스트림 호출 수로 나눠야 합니다.
무료 티어는 RPM·TPM·RPD를 대시보드에서 캡처한 뒤 소프트 캡만 코드에 넣는다
4단계 | 구조화 JSON 출력으로 재호출을 줄이기
무료 티어에서 한도를 갉아먹는 패턴 중 하나는 “대충 텍스트로 받고, 파싱 실패하면 다시 호출”입니다. 스키마를 고정하면 재호출이 줄고, 서버에서 검증 실패 시 사용자에게 수정 폼을 보여 주는 편이 RPD에 유리합니다.
json-mode.mjs — JSON MIME + 로컬 검증
import 'dotenv/config'
import { GoogleGenAI } from '@google/genai'
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
const model = process.env.GEMINI_MODEL || 'gemini-2.5-flash'
const prompt = `다음 이슈를 JSON만 반환.
keys: severity(low|mid|high), summary(80자 이내), actions(문자열 배열 최대 3).
이슈: 배포 후 /api/health 가 간헐적 500.`
const res = await ai.models.generateContent({
model,
contents: prompt,
config: {
temperature: 0.1,
maxOutputTokens: 256,
responseMimeType: 'application/json',
},
})
const raw = res.text || '{}'
let obj
try {
obj = JSON.parse(raw)
} catch {
// 업스트림 재호출 금지: 로컬에서 실패 처리
console.error('parse_fail', raw.slice(0, 200))
process.exit(2)
}
const ok =
['low', 'mid', 'high'].includes(obj.severity) &&
typeof obj.summary === 'string' &&
Array.isArray(obj.actions) &&
obj.actions.length <= 3
if (!ok) {
console.error('schema_fail', obj)
process.exit(3)
}
console.log('structured_ok', obj)
responseMimeType·스키마 옵션 이름은 SDK·모델 버전에 따라 다를 수 있습니다. 문서의 structured output 절을 한 번 대조하세요. 필드가 깨져도 즉시 재호출하지 말고, 실패 카운터를 올려 소프트 캡과 같이 모니터링하는 편이 안전합니다. 장문 요약이 필요하면 Flash-Lite·Flash 계열로 먼저 돌리고, 실패 케이스만 상위 모델로 올리는 2단 라우팅도 한도 절약에 도움이 됩니다. 단가 표 읽기는 Flash thinking 가격을 참고하세요.
5단계 | 쿼터 미터 | 소프트 캡을 코드에 미러링
실한도에 딱 맞춰 쓰면 관리자 테스트·재시도·배치가 사용자 쿼터를 잠식합니다. 실한도의 70~80%를 소프트 캡으로 두고, 앱이 먼저 429를 내도록 만드는 편이 운영이 쉽습니다. 아래는 단일 프로세스용 인메모리 미터입니다. 서버리스·다중 인스턴스면 Redis 등 공유 저장소로 바꿔야 합니다.
quota-meter.mjs — 분당·일일 소프트 캡
// quota-meter.mjs
const softRpm = Number(process.env.SOFT_RPM || 10)
const softRpd = Number(process.env.SOFT_RPD || 1200)
// 단일 인스턴스 전제. 서버리스면 Redis/Upstash 로 교체
const state = {
dayKey: '',
dayCount: 0,
windowStart: 0,
windowCount: 0,
}
function ptDayKey(d = new Date()) {
// RPD 리셋 안내용 키 (운영 로그용). 실제 리셋은 구글 측 기준.
return d.toLocaleString('sv-SE', { timeZone: 'America/Los_Angeles' }).slice(0, 10)
}
export function assertQuota(n = 1) {
const now = Date.now()
const day = ptDayKey()
if (state.dayKey !== day) {
state.dayKey = day
state.dayCount = 0
}
if (now - state.windowStart >= 60_000) {
state.windowStart = now
state.windowCount = 0
}
if (state.windowCount + n > softRpm) {
const err = new Error('soft_rpm_exceeded')
err.code = 429
err.kind = 'soft_rpm'
throw err
}
if (state.dayCount + n > softRpd) {
const err = new Error('soft_rpd_exceeded')
err.code = 429
err.kind = 'soft_rpd'
throw err
}
state.windowCount += n
state.dayCount += n
return {
rpmLeft: softRpm - state.windowCount,
rpdLeft: softRpd - state.dayCount,
day,
}
}
// 사용 예
// assertQuota(1)
// await callGemini(...)
// 업스트림 429 와 soft_* 를 로그 필드로 구분할 것
call-with-meter.mjs — 미터 + 업스트림 호출 뼈대
import 'dotenv/config'
import { GoogleGenAI } from '@google/genai'
import { assertQuota } from './quota-meter.mjs'
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
const model = process.env.GEMINI_MODEL || 'gemini-2.5-flash'
export async function generateOnce(userText) {
const budget = assertQuota(1)
try {
const res = await ai.models.generateContent({
model,
contents: userText,
config: { maxOutputTokens: 256, temperature: 0.2 },
})
return {
text: res.text,
usage: res.usageMetadata || null,
budget,
upstream: 'ok',
}
} catch (e) {
// 구글 측 한도/네트워크와 soft 캡을 로그에서 구분
const status = e?.status || e?.code || 'unknown'
console.error('gemini_call_fail', { status, message: String(e.message || e), budget })
throw e
}
}
// CLI 테스트: node call-with-meter.mjs
if (import.meta.url === `file://${process.argv[1]}`) {
const out = await generateOnce('무료 티어 한도 테스트. 한 줄만.')
console.log(out)
}
로그에 kind: soft_rpm|soft_rpd와 업스트림 status를 같이 남기면, “우리 미터가 막은 것인지 / 구글이 막은 것인지”가 바로 갈립니다. 공개 데모 URL 앞에는 IP·유저 단위 추가 쿼터를 두는 편이 안전합니다. 키가 새면 한도도 같이 새는 문제는 서버 게이트 글의 체크리스트를 이어서 보세요.
6단계 | 429 백오프 | 재시도 상한을 코드에 박기
429를 받자마자 즉시 재시도하면 RPM·RPD를 더 빨리 태웁니다. 지수 백오프 + 지터 + 최대 2~3회가 기본입니다. 소프트 캡 429는 재시도하지 말고 사용자에게 “잠시 후” 메시지를 주는 편이 낫습니다.
backoff.mjs — 상한 있는 재시도
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms))
}
export async function withBackoff(fn, { maxAttempts = 3, baseMs = 800 } = {}) {
let last
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(attempt)
} catch (e) {
last = e
const status = e?.status || e?.code
const soft = e?.kind === 'soft_rpm' || e?.kind === 'soft_rpd'
if (soft) throw e // 소프트 캡은 재시도 금지
if (status !== 429 && status !== 503) throw e
if (attempt === maxAttempts) break
const jitter = Math.floor(Math.random() * 200)
const wait = baseMs * 2 ** (attempt - 1) + jitter
console.warn('retry', { attempt, wait, status })
await sleep(wait)
}
}
throw last
}
// 사용
// await withBackoff(() => generateOnce(text), { maxAttempts: 3 })
재시도 금지 패턴 | 프론트 onClick마다 업스트림 호출, 스트림 끊김 시 무한 재연결, 파싱 실패 시 자동 3회 재생성. 세 가지가 무료 티어 RPD를 하루 만에 비우는 단골입니다.
7단계 | 사이드 프로젝트 일일 용량 식
“가입자 1,000명이면 버틸까?”보다 기능 1회 = 업스트림 몇 호출인가를 먼저 적습니다. 아래 표는 연습용입니다. RPD·소프트 캡은 본인 스냅샷으로 바꾸세요.
// cost-scenario.mjs — 숫자는 예시. 배포 전 공식 Pricing 으로 교체
const USD_KRW = 1400
const VAT_RATE = 0.10 // 카드 명세 기준이 다를 수 있음 → 시나리오용
// 예시: Flash 계열 근사 (1M 토큰당 USD) — 반드시 공식 표로 교체
const PRICE = {
flash_lite: { in: 0.30, out: 2.50 },
flash: { in: 0.50, out: 3.00 },
}
function monthlyUsd(model, inTokens, outTokens) {
const p = PRICE[model]
return (inTokens / 1e6) * p.in + (outTokens / 1e6) * p.out
}
function toKrw(usd, withVat = true) {
let krw = usd * USD_KRW
if (withVat) krw *= 1 + VAT_RATE
return Math.round(krw)
}
const scenarios = [
['flash_lite', 8_000_000, 1_500_000],
['flash', 8_000_000, 1_500_000],
['flash_lite', 30_000_000, 6_000_000],
]
for (const [model, inn, out] of scenarios) {
const usd = monthlyUsd(model, inn, out)
console.log(model, `USD ${usd.toFixed(2)}`, `KRW~${toKrw(usd)} (부가세 시나리오 포함)`)
}
// 출력을 스프레드시트에 붙이고, 카드 명세 1회와 대조
골격 해석 예 | Flash-Lite 입력 8M·출력 1.5M을 위 예시 단가로 잡으면 USD 약 6.15, 환율 1,400원이면 약 8,600원, 부가세 10% 시나리오를 더하면 약 9,500원 전후입니다. 이 숫자는 교육용이며 실제 청구·환율·수수료와 다를 수 있습니다. MVP·기존 키 코드 유지는 개발자 API 결제 연동이 단순하고, 조직 규정·VPC가 필요하면 Vertex를 검토하는 흐름이 일반적입니다.
주의 | 원화·부가세 예시는 의사결정용 시나리오입니다. “월 몇 원 확정”처럼 단정하지 말고, 스프레드시트에 단가 조회일·환율 조회일을 적으세요. 실제 기준은 구글 청구서·카드 명세입니다.
배포 전 10분 체크 | 무료 티어가 깨지는 지점
키가 클라이언트 번들·공개 저장소에 없는가.
Rate Limit 화면 숫자를 quota-snapshot.md에 캡처했는가.
소프트 RPD·RPM이 실한도보다 낮은가.
프록시 앞에 인증 또는 남용 방지(IP·유저 쿼터)가 있는가.
업스트림 429와 소프트 캡 429를 로그에서 구분하는가.
재시도가 무한 루프가 아닌가 (최대 2~3회).
usageMetadata를 2주간 저장하는가 (유료 전환 근거).
모델 ID가 환경 변수로 고정돼 있는가.
구·신 SDK를 한 파일에서 섞지 않았는가.
유료 전환 시 약관(데이터 사용)·단가표를 다시 읽었는가.
이 목록을 PR 템플릿 한 줄로 넣으면 “일단 키만 넣고 배포” 사고가 줄어듭니다. 내부 도구 수준이면 무료만으로도 충분한 경우가 많고, 공개 서비스 SLA가 생기면 결제 연동을 미루지 않는 편이 운영 비용이 낮습니다.
한도·가격·모델 ID·SDK API는 2026년 8월 조회 기준으로 작성했으며, 배포 전 공식 페이지에서 재확인하세요.
자주 묻는 질문
제미나이 무료 API는 카드 없이 쓸 수 있나요?
네. AI 스튜디오에서 구글 계정으로 키를 만들면, 무료 티어 한도 안에서는 결제 정보 없이 호출할 수 있는 경우가 일반적입니다. 한도를 넘기면 보통 자동 과금이 아니라 429로 거절됩니다. 상위 한도·유료 전용 모델·약관상 유료 조건이 필요할 때 결제를 연동하면 됩니다.
키를 여러 개 만들면 일일 한도가 늘어나나요?
한도는 키마다 따로가 아니라 프로젝트 단위로 잡히는 경우가 일반적입니다. 같은 프로젝트의 키를 나눠 써도 RPM·RPD는 합산됩니다. 한도를 늘리려면 사용 티어 승급(결제·누적 사용)이나 공식 한도 상향 요청 경로를 확인하세요.
구 generative-ai 패키지와 @google/genai 중 무엇을 쓰나요?
신규 실습·신규 서비스는 공식 문서가 안내하는 @google/genai 쪽을 기준으로 맞추는 편이 유지보수에 유리합니다. 레거시 예제를 복사할 때는 import 경로와 클라이언트 생성 코드를 한 세트로 맞추고, 한 파일에서 구·신을 섞지 마세요.
소프트 캡을 실한도보다 낮게 잡는 이유는?
실한도에 딱 맞춰 쓰면 관리자 테스트·재시도·배치가 사용자 쿼터를 잠식합니다. 70~80% 소프트 캡을 두면 429를 앱이 먼저 제어할 수 있고, 남은 구간은 긴급 배포·수동 점검용으로 남길 수 있습니다.
RPD가 언제 리셋되나요?
공식 문서는 일일 요청(RPD) 쿼터가 태평양 시간(PT) 자정에 리셋된다고 안내하는 경우가 많습니다. 국내 저녁 피크와 리셋 시각이 어긋날 수 있으니, 배치 작업은 리셋 직후로 미루는 편이 안전합니다. zoneinfo(America/Los_Angeles) 사용을 권장합니다.
AI 스튜디오 유료와 Vertex 중 무엇을 고르면 되나요?
빠른 MVP·기존 개발자 API 코드 유지는 결제 연동(티어 상향)이 단순합니다. 조직 IAM·VPC·감사·엔터프라이즈 계약이 필요하면 Vertex 쪽을 검토합니다. 둘 다 제미나이 모델 계열을 쓸 수 있지만 인증·운영·청구 구조가 다릅니다.
원화 비용은 어떻게 어림하나요?
공식 단가(USD/1M tokens) × 월 토큰 ÷ 100만 × 환율로 먼저 잡고, 카드 명세서로 부가세·수수료를 한 번 보정합니다. 2주간 usageMetadata를 모아 30일로 환산하면 감이 옵니다. 환율·단가는 변동하므로 월 1회 갱신을 권장합니다.