Unkey 완벽 가이드 | API 키·RBAC·레이트 리밋·Next.js·tRPC·셀프 호스팅
이 글의 핵심
Unkey는 API 키 관리, 레이트 리밋(rate limiting), 사용량 추적, 권한(RBAC)을 한 번에 다루는 API 개발 플랫폼입니다. 직접 Redis·해시 저장소·분석 파이프라인을 운영하는 대신, 검증 한 번으로 만료·쿼터·권한·레이트 리밋을 함께 판정할 수 있습니다.
이 글에서는 워크스페이스·API 네임스페이스·아이덴티티(identity) 같은 핵심 개념을 정리하고, 키 생성·검증 흐름, 크레딧·자동 리필, Next.js(App Router) 와 tRPC 연동 패턴, 셀프 호스팅 시 고려할 점, 멀티 테넌트 SaaS에서의 실전 설계까지 다룹니다.
참고: 제품 기능과 API는 업데이트될 수 있으므로, 최종 스펙은 Unkey 공식 문서를 함께 확인하시기 바랍니다.
들어가며: “API 키는 만들었는데 운영이 어렵습니다”
실무 문제 시나리오
시나리오 1: 키만으로는 부족합니다
만료·비활성화·요금제별 한도·엔드포인트별 권한을 애플리케이션 코드 곳곳에 흩어 놓으면 유지보수가 어렵습니다. Unkey는 검증 시점에 정책을 일괄 적용하는 방향으로 설계하는 것이 자연스럽습니다.
시나리오 2: 레이트 리밋 인프라가 부담입니다
Redis 클러스터와 슬라이딩 윈도우 로직을 직접 운영하는 대신, 식별자(사용자 ID, IP, 키 ID 등) 단위의 서버리스 레이트 리밋을 API로 호출하는 방식을 선택할 수 있습니다.
시나리오 3: 과금·쿼터를 제품과 맞추기 어렵습니다
요청 수 기반 과금이면 크레딧(usage limits) 과 자동 리필, 키·아이덴티티 단위 메타데이터를 활용해 플랜·조직 단위로 정렬할 수 있습니다.
1. Unkey란 무엇인가
Unkey는 “키 발급”에 그치지 않고, 검증(verify) 과정에서 유효성·레이트 리밋·권한·사용량까지 한 번에 처리하도록 만든 플랫폼입니다. 공식 소개에 따르면, 저장 시 평문 키를 보관하지 않고 해시 후 저장하는 방향을 취합니다(Introduction).
한 줄 요약: 고객에게 나가는 API 키의 생명주기와 정책을 중앙에서 관리하고, 애플리케이션은 검증 API/SDK에 맡긴다.
2. 핵심 개념 정리
2.1 워크스페이스(Workspace)
최상위 테넌트 경계입니다. 팀·결제·루트 키(root key)·API 리소스가 워크스페이스 단위로 묶입니다. 운영·스테이징을 별도 워크스페이스로 나누는 전략도 흔합니다.
2.2 루트 키(Root key)와 API 키
- 루트 키: Unkey 관리 API(
키 생성,권한 설정,레이트 리밋 네임스페이스등)를 호출할 때 사용합니다. 서버 사이드 전용이며, 브라우저나 모바일 앱에 넣으면 안 됩니다. - API 키(고객 키): 최종 사용자·파트너·내부 서비스가 당신의 비즈니스 API를 호출할 때 제시하는 자격 증명입니다. 검증은
verifyKey계열 API/SDK로 수행합니다.
2.3 API(API 네임스페이스)
하나의 공개 API 제품(또는 환경)에 대응하는 논리적 묶음입니다. 키는 특정 API에 귀속되며, 분석·정책을 API 단위로 나누기 좋습니다.
2.4 아이덴티티(Identity)
여러 API 키를 한 사용자·조직·테넌트 아래로 묶을 때 사용합니다. 동일 externalId(예: org_acme)를 쓰는 키들이 레이트 리밋 풀을 공유하는 식의 멀티 테넌트 패턴에 적합합니다(Introduction 예시).
2.5 권한·역할(RBAC)
워크스페이스에 퍼미션(permission) 과 롤(role) 을 정의하고, API 키에 직접 부여하거나 롤을 통해 묶습니다. 검증 시 permissions: [...] 를 넘기면 부족한 권한은 구조화된 코드(예: INSUFFICIENT_PERMISSIONS)로 거절할 수 있습니다(Cookbook: Check Permissions).
2.6 스탠드얼론 레이트 리밋
API 키와 무관하게 임의의 식별자(IP, 사용자 ID 등)에 대해 @unkey/ratelimit 등으로 제한할 수 있어, 로그인 세션·웹훅·내부 배치까지 동일한 인프라로 보호할 수 있습니다(Rate limiting 소개).
3. API 키 생성과 검증
3.1 생성 시 자주 쓰는 옵션
- 만료(expiration): 트라이얼·임시 파트너 키.
- 메타데이터: 요금제,
tenantId, 이메일 등 JSON 메타데이터 — 대시보드·청구 로직과 연동하기 좋습니다. - 크레딧·리필: 월간 요청 한도, 자동 리필 — 사용량 기반 과금 모델에 활용(Introduction).
- 레이트 리밋: 키 또는 아이덴티티에 이름 붙은 한도(예: 시간당 N회).
- IP 화이트리스트: 네트워크 단에서 추가 제한이 필요할 때(문서 인덱스: IP Whitelisting).
3.2 TypeScript SDK로 생성·검증
@unkey/api 클라이언트를 사용합니다. 루트 키는 환경 변수로만 주입합니다.
import { Unkey } from "@unkey/api";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
// 1) 키 생성 (서버·관리 작업 전용)
export async function issueCustomerKey(apiId: string, externalId: string) {
const { data } = await unkey.keys.create({
apiId,
externalId,
meta: { plan: "pro" },
// ratelimits, credits 등 필요 시 추가
});
// data.key 는 이 시점에만 평문으로 전달 가능 — 클라이언트에 안전하게 전달할 것
return data;
}
// 2) 요청마다 검증
export async function verifyIncomingKey(rawKey: string) {
const { data } = await unkey.keys.verifyKey({ key: rawKey });
if (!data.valid) {
return { ok: false as const, code: data.code };
}
return { ok: true as const, identity: data.identity, meta: data.meta };
}
왜 이렇게 나누는가: 생성은 관리 권한이 있는 백엔드(결제 웹훅, 관리자 콘솔)에서만 수행하고, 검증은 모든 API 경계에서 반복 호출합니다. 실패 코드(NOT_FOUND, EXPIRED, RATE_LIMITED, USAGE_EXCEEDED 등)에 따라 HTTP 상태와 메시지를 매핑하면 운영이 단순해집니다.
3.3 검증 시 권한까지 함께 요구하기
민감한 작업(삭제, 관리자 전용 엔드포인트)에는 verifyKey 호출에 permissions 배열을 넘깁니다. 문서 예시는 admin.delete 같은 문자열 슬러그를 사용합니다(Next.js Cookbook).
4. Rate Limiting과 Usage Tracking
4.1 키에 붙는 레이트 리밋
키 생성 시 ratelimits 배열로 이름·한도·기간(ms)을 지정할 수 있습니다. 아이덴티티를 쓰면 같은 조직의 여러 키가 동일 풀을 공유해, 키를 여러 개 발급해도 조직 단위 쿼터를 유지할 수 있습니다.
4.2 크레딧(Usage limits)
“이번 달 N회까지” 같은 누적 사용량 상한에 가깝게 동작합니다. 검증마다 차감·차단 여부가 판정되며, 플랜 업그레이드 시 API로 상한을 올리는 흐름과 잘 맞습니다(Remaining / Refill 문서 트리).
4.3 스탠드얼론 레이트 리밋
키 인증과 별개로, 공개 폼·로그인 API처럼 키가 없는 트래픽에 대해 identifier 기준으로만 제한할 수 있습니다. Redis를 직접 띄우지 않고도 글로벌하게 일관된 카운터를 기대할 수 있는 설계를 목표로 합니다(Introduction).
4.4 분석·SQL 쿼리
워크스페이스에서 검증 로그·사용 패턴을 SQL로 조회하는 흐름(Analytics)이 제공됩니다(Analytics Overview). 청구·이상 징후 탐지·플랜별 사용량 리포트에 연결할 수 있습니다.
5. 권한과 역할 관리
5.1 모델
- Permission: 세분화된 액션(예:
documents.read,documents.write). - Role: 퍼미션 묶음(예:
editor,viewer). - API 키: 직접 퍼미션을 갖거나 롤을 통해 간접적으로 갖습니다.
RBAC 예제 워크스루는 공식 Authorization 섹션을 참고하십시오(Authorization Overview).
5.2 검증 단계에서의 판정
애플리케이션은 “이 키가 유효한가”만이 아니라 “이 엔드포인트에 필요한 권한을 갖는가” 를 Unkey에 위임할 수 있습니다. 부족하면 INSUFFICIENT_PERMISSIONS 로 구분해 401과 403 을 나누기 쉽습니다.
5.3 운영 팁
- 퍼미션 문자열은 일관된 네이밍 규칙(도메인.동작)을 쓰면 대시보드·코드 검색이 쉬워집니다.
- 롤은 요금제별 템플릿으로 두고, 키 생성 시 롤만 부여하면 온보딩 스크립트가 단순해집니다.
6. SDK와 통합
6.1 Next.js(App Router) — @unkey/nextjs
가장 단순한 방법은 withUnkey 로 Route Handler를 감싸는 것입니다. req.unkey 에 검증 결과가 붙고, 기본은 Authorization: Bearer 입니다(Next.js Cookbook).
import { withUnkey, NextRequestWithUnkeyContext } from "@unkey/nextjs";
export const POST = withUnkey(
async (req: NextRequestWithUnkeyContext) => {
const externalId = req.unkey.data.identity?.externalId;
return Response.json({ ok: true, tenant: externalId });
},
{
rootKey: process.env.UNKEY_ROOT_KEY!,
// getKey: (req) => req.headers.get("x-api-key"),
// handleInvalidKey, onError 로 UX 통일
},
);
주의: UNKEY_ROOT_KEY 는 서버 전용입니다. NEXT_PUBLIC_ 접두사를 붙이지 마십시오.
수동 검증이 필요하면 동일 Cookbook의 Unkey 클라이언트와 verifyKey 예제를 그대로 옮겨 미들웨어 패턴(withAuth)으로 재사용할 수 있습니다.
6.2 tRPC — 컨텍스트와 미들웨어
공식 “tRPC 전용” 패키지보다, 컨텍스트에서 헤더를 읽고 프로시저 미들웨어에서 검증하는 패턴이 일반적입니다. 아래는 App Router + fetch adapter 기준 개념 예시입니다(프로젝트의 createContext 시그니처에 맞게 조정하십시오).
import { initTRPC, TRPCError } from "@trpc/server";
import { Unkey } from "@unkey/api";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
const t = initTRPC.context<{ authorization: string | null }>().create();
const enforceApiKey = t.middleware(async ({ ctx, next }) => {
const header = ctx.authorization;
if (!header?.startsWith("Bearer ")) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Missing API key" });
}
const key = header.slice(7);
const { data } = await unkey.keys.verifyKey({ key });
if (!data.valid) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: data.code ?? "Invalid API key",
});
}
return next({ ctx: { ...ctx, unkey: data } });
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const apiKeyProcedure = t.procedure.use(enforceApiKey);
특정 뮤테이션에서만 permissions 를 요구하려면, 해당 프로시저 안에서 unkey.keys.verifyKey({ key, permissions: ["items.delete"] }) 를 한 번 더 호출하거나, 미들웨어를 파라미터화한 scopedProcedure(permissions) 팩토리를 두면 됩니다.
Next.js와의 접점: Route Handler에서 tRPC를 호출할 때 헤더를 그대로 넘기도록 createContext 를 구성해야 합니다. 배치·서버 간 호출에서는 서비스 계정 키를 별도 발급해 회전하는 편이 안전합니다.
6.3 기타 런타임
- Hono:
@unkey/hono미들웨어(문서 인덱스). - Go / Python: 공식 SDK로 동일한 생성·검증 패턴을 유지할 수 있습니다.
7. 셀프 호스팅
Unkey는 오픈소스 모노레포(github.com/unkeyed/unkey)로 제공됩니다. 클라우드 관리형을 쓰지 않고 직접 운영하려면 데이터베이스·캐시·(선택) 분석 스토어 등 의존 서비스를 함께 준비해야 하며, 최소 사양·구성은 릴리스와 docker-compose 정의에 따라 달라질 수 있습니다.
실무 체크리스트
- 비밀 관리: 암호화 키, DB 자격 증명, S3 호환 스토리지(필요 시)를 시크릿 매니저에 보관합니다.
- 업그레이드 경로: 마이그레이션 스크립트·다운타임 없는 배포 전략을 문서화합니다.
- 관측 가능성: 검증 실패율, 레이트 리밋 거부, 지연 시간을 메트릭으로 수집합니다.
- 규제: 데이터 레지던시·감사 로그 요구가 있으면 셀프 호스팅이 유리할 수 있습니다(Audit log).
자세한 설치 단계는 저장소의 안내와 최신
README/ 배포 문서를 따르십시오.
8. 실전 SaaS API 관리
8.1 테넌트 모델
- B2B:
identity.externalId = orgId로 묶고, 조직 단위 레이트 리밋·크레딧을 공유합니다. - B2C: 사용자당 키를 여러 개 두기보다 한 사용자·여러 디바이스면 동일 아이덴티티 아래 키를 발급합니다.
8.2 요금제와 정책
티어 구독 Cookbook, 사용량 기반 과금에서 제안하듯, Free / Pro / Enterprise 별로 키당 레이트 리밋·크레딧·권한 세트를 다르게 두고, 결제 웹훅에서 updateKey·updateKeyCredits 로 동기화하는 패턴이 흔합니다.
8.3 키 회전(Reroll)
유출 의심·정기 회전 시 설정·메타데이터를 유지한 채 키 문자열만 교체하는 reroll 흐름을 쓰면 고객 다운타임을 줄일 수 있습니다(Key Rerolling).
8.4 마이그레이션
기존 해시된 키를 다른 제공자에서 가져오는 마이그레이션 API가 별도로 준비되어 있습니다(Migrate Keys). 사용자에게 키를 재발급하지 않아도 되는지 여부는 해시 호환성에 달립니다.
8.5 엣지·프록시
Cloudflare Workers 등 엣지에서 검증하는 Cookbook이 있어, 지역별 지연을 줄이려는 경우 참고할 수 있습니다(Cloudflare Workers). Sentinel 역프록시·정책 기반 인증은 인프라를 Unkey 쪽에 더 맡기는 접근입니다(Sentinel).
9. 정리
Unkey는 API 키의 생명주기와 접근 정책을 중앙에서 다루게 해 주며, 검증 한 번으로 만료·쿼터·RBAC·레이트 리밋을 동시에 판정할 수 있습니다. Next.js는 @unkey/nextjs 로 빠르게 도입할 수 있고, tRPC는 컨텍스트 + 미들웨어로 동일한 계약을 유지하면 됩니다. SaaS에서는 아이덴티티·크레딧·롤을 요금제와 정렬해 두면 운영 자동화가 쉬워집니다.
다음 단계로는 워크스페이스에서 테스트 API 네임스페이스를 만들고, 스테이징에서 verifyKey 실패 코드별 HTTP 매핑표를 한 번만 정해 두는 것을 권합니다.