End-Customer Budgets
리셀러 모드에서 고객별 예산을 설정하고 X-Customer-Id 헤더로 사용량을 추적하세요.
개요 / Overview
리셀러 모드는 하나의 워크스페이스와 API 키로 여러 최종 고객(end-customer)에게 AI API를 제공할 수 있는 모델입니다. 각 고객에게 독립적인 선불 예산(cap)을 부여하고, 크레딧은 1:1로 차감됩니다. 고객(customer)은 여러분의 내부 사용자 또는 서비스 계정을 의미하며, 실제 개인 정보(이메일 등)를 사용하지 않습니다.
하나의 워크스페이스
API 키 하나로 모든 고객 요청 처리
고객별 예산
고객마다 독립적인 크레딧 cap 설정
사용량 위임
고객별 사용 내역을 API로 직접 조회
핵심 용어
- • cap — 고객이 사용할 수 있는 누적 크레딧 한도. topup으로만 증가합니다.
- • meter(미터) — 고객별 누적 사용량(
spent) 카운터. 한도 enforcement용 참고값(advisory)이며, 정산·감사의 기준은 usage 로그입니다. - • 워크스페이스 풀 — 실제 크레딧(money)이 차감되는 워크스페이스 공유 잔액. 고객 미터는 돈을 움직이지 않습니다.
X-Customer-Id는 어디에 적용되나
하나의 X-Customer-Id 헤더가 세 가지 표면에서 각각 다른 역할을 합니다. 리셀러 백엔드는 최종 고객의 요청을 프록시할 때 이 헤더를 그대로 전달하면 됩니다.
Predictions
과금 귀속 — 예측 사용량이 해당 고객 미터에 계상되고 예산(cap)이 적용됩니다. (아래 워크플로우 요약 참고)
Files
고객별 파일 조회 + 저장 격리 — 업로드/재서명이 그 고객의 파일로 한정됩니다. File Upload 문서
Databases
고객별 문서 격리 — 문서 읽기/쓰기/검색이 그 고객으로 필터링됩니다. 리셀러 팀에서는 X-Customer-Id가 필수(fail-closed, 누락 시 400 customer_scope_required)이며, 전체 데이터셋 접근은 X-Customer-Scope: all 헤더를 명시해야 합니다. Databases 문서
빠른 시작 / Quick Start
리셀러 모드가 활성화되어 있다면(Step 1), 아래 4개 요청으로 첫 고객 생성부터 청구까지 바로 따라할 수 있습니다.
# 1) 상태 확인 — reseller_mode: true 인지 확인
curl https://api.core.today/v1/reseller/status \
-H "X-API-Key: cdt_your_api_key"
# 2) 고객 생성 (선불 예산 1,000 크레딧)
curl -X POST https://api.core.today/v1/reseller/customers \
-H "Content-Type: application/json" -H "X-API-Key: cdt_your_api_key" \
-d { "customer_id": "user_abc123", "cap": 1000 }
# 3) 고객 예산으로 AI 요청 — X-Customer-Id 헤더만 추가
curl -X POST https://api.core.today/v1/predictions \
-H "Content-Type: application/json" -H "X-API-Key: cdt_your_api_key" \
-H "X-Customer-Id: user_abc123" \
-d { "model": "black-forest-labs/flux-schnell", "input": { "prompt": "hello" } }
# 4) 월말 정산 — 고객별 청구 CSV 다운로드
curl "https://api.core.today/v1/reseller/usage/summary?start_date=2026-06-01T00:00:00Z&end_date=2026-07-01T00:00:00Z&format=csv" \
-H "X-API-Key: cdt_your_api_key" -o billing.csv리셀러 모드 활성화
리셀러 모드는 워크스페이스 단위로 활성화됩니다. 콘솔의 고객(Customers) 페이지에서 "리셀러 모드 신청" 버튼으로 직접 신청하면 운영팀 승인 후 활성화됩니다. API로 신청하려면 POST /teams/{teamId}/reseller-request (Clerk JWT 인증, owner/admin 전용)를 호출하고, 진행 상태는 GET /teams/{teamId}/reseller-request로 확인할 수 있습니다. 문의가 필요하면 support@core.today로 연락할 수 있습니다.
참고: 리셀러 모드가 비활성화된 워크스페이스에서X-Customer-Id헤더를 보내도 무시됩니다. 예산 기능은 활성화 후에만 동작하며, 활성화된 뒤에는 이 헤더가 필수가 됩니다 (Step 4 참고).
워크스페이스 상태 확인
관리 엔드포인트를 호출하기 전에 GET /reseller/status로 워크스페이스의 리셀러 상태와 크레딧 잔액을 확인하세요. 이 엔드포인트는 리셀러 모드 여부와 무관하게 모든 유효한 API 키로 호출할 수 있습니다.
curl https://api.core.today/v1/reseller/status \
-H "X-API-Key: cdt_your_api_key"
# Response — reseller_mode가 false여도 필드 구성은 동일합니다
{
"reseller_mode": true,
"customer_count": 3,
"max_customers": 10000,
"workspace_credits": 5000.0,
"committed_cap": 12000.0,
"overcommit_ratio": 2.4,
"overcommitted": true
}사용 팁: 백엔드 서버가 시작될 때 reseller_mode: true를 확인하여 관리 API를 사용할 수 있는지 사전 검증하세요. workspace_credits가 0에 근접하면 워크스페이스를 충전해야 모든 고객 요청이 정상 처리됩니다.
초과 배정(overcommit) 운영 참고: committed_cap은 활성(active) 고객 cap의 합계입니다. 모든 고객 cap의 합이 워크스페이스 풀을 초과하는 것은 허용되지만(초과 배정), 이 경우 overcommitted: true가 되며 (overcommit_ratio= committed ÷ pool, 풀이 0이면 null) — 고객별 예산이 남아 있어도 워크스페이스 풀이 먼저 소진되면 reseller_account_insufficient로 모든 고객 요청이 차단됩니다. 워크스페이스 풀을 충전하거나 고객 cap을 줄이세요. workspace.pool_low 웹훅(아래 참고)이 이 상황의 조기 경보입니다.
고객 예산 생성
고객 ID와 초기 크레딧 cap을 지정하여 고객 지갑을 만듭니다. 팀 ID는 API 키에서 자동으로 결정되므로 경로에 포함하지 않습니다.
customer_id 규칙
- • 허용 문자:
[A-Za-z0-9_.-] - • 최대 128자
- • 불투명(opaque) ID 사용 — 이메일, 전화번호 등 개인 정보 금지
- • 예:
user_abc123,acct.98765
curl -X POST https://api.core.today/v1/reseller/customers \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-d '{
"customer_id": "user_abc123",
"cap": 2000,
"name": "Acme Corp",
"external_ref": "optional-your-internal-ref"
}'
# Response (201 Created)
{
"customer_id": "user_abc123",
"cap": 2000,
"spent": 0,
"remaining": 2000,
"status": "active",
"name": "Acme Corp",
"external_ref": "optional-your-internal-ref",
"monthly_allowance": null,
"next_reset_at": null,
"reset_due": false,
"created_at": "2026-07-01T00:00:00+00:00",
"updated_at": "2026-07-01T00:00:00+00:00"
}| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| customer_id | string | Yes | 고객 식별자 (opaque, PII 금지) |
| cap | number | Yes | 초기 크레딧 한도 (0 이상, 최대 1,000,000) |
| external_ref | string | No | 내부 참조 문자열 (임의 용도, 최대 512자) |
| name | string | No | 사람이 읽는 표시 이름 (최대 200자). 콘솔 고객 목록에 표시됩니다. |
| monthly_allowance | number | No | 월간 예산 (0 초과, 최대 1,000,000) — Step 8 "월간 예산" 참고. 지정 시 응답에 monthly_allowance, next_reset_at이 포함됩니다. |
표시 메타데이터 수정 (PATCH)
표시 메타데이터는 PATCH /reseller/customers/{id}로 부분 수정합니다 — 수정 가능한 필드는 name / external_ref뿐입니다. cap은 이 엔드포인트로 변경할 수 없습니다 — 증액은 멱등한 topup을, 월간 예산은 PUT .../budget을 사용하세요.
curl -X PATCH https://api.core.today/v1/reseller/customers/user_abc123 \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-d '{ "name": "Acme Corp (renamed)", "external_ref": "crm-4412" }'
# Response — full wallet (remaining = cap - spent)
{
"customer_id": "user_abc123",
"cap": 2000,
"spent": 350,
"remaining": 1650,
"status": "active",
"name": "Acme Corp (renamed)",
"external_ref": "crm-4412",
"monthly_allowance": null,
"next_reset_at": null,
"reset_due": false
}부분 수정 규칙: 생략한 필드는 그대로 유지됩니다. 명시적 null 또는 빈 문자열 ""은 해당 필드를 제거합니다. 두 필드를 모두 생략하면 400이 반환됩니다.
대량 온보딩 (Bulk Create)
고객이 많은 경우 POST /reseller/customers/bulk로 최대 100개 항목을 한 번에 생성할 수 있습니다. 부분 성공(partial success)방식으로, 항목별로 독립 처리되며 성공(created)과 실패(errors)가 분리되어 반환됩니다.
docs.endCustomerBudgets.step3.bulkCode부분 성공 처리: 항목 하나가 실패해도 전체가 롤백되지 않습니다. 응답의 errors배열을 반드시 확인하여 실패 항목만 재시도하세요. 단, customer_limit_exceeded (팀당 최대 10,000 고객 초과)가 발생하면 나머지 항목은 시도되지 않고모두 errors로 보고됩니다. 요청당 100개 항목 제한을 초과하면 요청 전체가 거부됩니다 (422).
고객 예산으로 요청 전송
AI 요청 시 X-Customer-Id 헤더를 추가하면 해당 요청의 크레딧이 고객 예산에 귀속됩니다. 이미지·비디오·오디오 등 prediction API와 LLM/chat 게이트웨이 경로 모두 고객별 예산이 완전히 적용됩니다.
헤더 필수 (fail-closed): 리셀러 모드가 활성화된 워크스페이스에서는 모든 prediction/LLM 요청에 X-Customer-Id 헤더가 필수입니다. 헤더가 없거나 비어 있거나 형식이 잘못되면 400 invalid_customer_id로 거부됩니다. 리셀러 워크스페이스 자체 소비용 트래픽도 내부 customer_id 하나를 만들어 귀속시키세요.
# 이미지 생성 요청 (고객 예산 적용)
curl -X POST https://api.core.today/v1/predictions \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-H "X-Customer-Id: user_abc123" \
-d {
"model": "black-forest-labs/flux-schnell",
"input": {
"prompt": "A futuristic city skyline"
}
}
# LLM/chat 요청 (게이트웨이 경유) — 고객 예산이 동일하게 적용됩니다
curl -X POST https://api.core.today/llm/openai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-H "X-Customer-Id: user_abc123" \
-d {
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello!"}]
}동작 방식 (차감 순서): ① 먼저 고객 미터에서 cap내 예산을 확인·차감합니다 (돈은 이동하지 않음 — 한도 초과 고객을 빠르게 거절). ② 이어서 워크스페이스 풀에서 실제 크레딧이 차감됩니다. 고객 예산이 부족하면 402 customer_wallet_insufficient, 등록되지 않은 고객 ID면 402 customer_not_found가 반환됩니다 — 먼저 POST /reseller/customers로 등록하세요. 요청이 실패·취소되어 환불되면 워크스페이스 풀과 고객 미터가 모두 복구됩니다.
고객 예산 충전 (Top-up)
고객의 예산이 소진되면 충전합니다. 요청은 멱등(idempotent)하므로 네트워크 오류 시 안전하게 재시도할 수 있습니다. idempotency_key(1~128자, [A-Za-z0-9_.:-])가 같은 요청은 한 번만 처리됩니다. 응답의 applied 필드는 true (크레딧 추가됨) 또는 false (중복/멱등 no-op)를 나타냅니다.
curl -X POST https://api.core.today/v1/reseller/customers/user_abc123/topup \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-d '{
"amount": 1000,
"idempotency_key": "topup-2026-06-30-001"
}'
# Response
{
"applied": true,
"cap": 3000
}멱등성: 동일한 idempotency_key로 중복 요청 시 applied: false를 반환하며 크레딧이 두 번 추가되지 않습니다. UUID v4 또는 타임스탬프 기반 고유 키를 권장합니다.
일괄 충전 (Bulk Top-up)
POST /reseller/customers/topup/bulk로 최대 100개 항목 을 한 번에 충전할 수 있습니다. 항목별 idempotency_key로 각각 멱등 처리되며, 부분 성공 방식입니다.
docs.endCustomerBudgets.step5.bulkCode잔액 조회, 목록, 삭제
고객별 잔액을 직접 조회할 수 있어 별도의 잔액 저장소를 구축할 필요가 없습니다. 고객을 삭제하면 지갑이 소프트 삭제(비활성화)됩니다.
# 잔액 조회
curl https://api.core.today/v1/reseller/customers/user_abc123 \
-H "X-API-Key: cdt_your_api_key"
# Response — 월간 예산 3필드 포함 (monthly_allowance, next_reset_at, reset_due)
{
"customer_id": "user_abc123",
"cap": 3000,
"spent": 450,
"remaining": 2550,
"status": "active",
"external_ref": null,
"monthly_allowance": null,
"next_reset_at": null,
"reset_due": false
}
# 전체 목록 조회 (커서 페이지네이션, limit 1..500 기본 100)
curl "https://api.core.today/v1/reseller/customers?limit=100" \
-H "X-API-Key: cdt_your_api_key"
# Response — next_cursor가 null이면 마지막 페이지
{
"items": [
{
"customer_id": "user_abc123",
"cap": 3000,
"spent": 450,
"status": "active",
"external_ref": null,
"monthly_allowance": null,
"next_reset_at": null,
"reset_due": false,
...
}
],
"next_cursor": "eyJQSyI6...opaque..."
}
# 다음 페이지 — 받은 next_cursor를 그대로 cursor로 전달
curl "https://api.core.today/v1/reseller/customers?cursor=eyJQSyI6...opaque...&limit=100" \
-H "X-API-Key: cdt_your_api_key"
# 고객 삭제 (소프트 삭제 → 204 No Content)
curl -X DELETE https://api.core.today/v1/reseller/customers/user_abc123 \
-H "X-API-Key: cdt_your_api_key"페이지네이션 참고: next_cursor는 불투명(opaque) 토큰입니다 — 파싱하거나 직접 만들지 말고 그대로 반환하세요. 잘못된 커서는 400 "Invalid cursor"로 거부됩니다. 목록에는 소프트 삭제된 지갑(status: "deleted")도 포함되므로 필요하면 클라이언트에서 필터하세요. 단건 조회(GET /customers/{id})는 삭제된 고객에 대해 404를 반환합니다.
고객 일시 정지 / 재개
미납 고객은 삭제하지 않고 일시 정지(suspend)하여 사용 이력을 보존한 채 지출만 즉시 차단할 수 있습니다. 정지된 고객의 요청은 402 customer_suspended 에러와 함께 조치가 필요한 메시지를 반환합니다. 결제가 확인되면 재개(reactivate)로 즉시 복구하세요.
# 고객 일시 정지
curl -X POST https://api.core.today/v1/reseller/customers/user_abc123/suspend \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"customer_id": "user_abc123",
"status": "suspended"
}
# 고객 재개
curl -X POST https://api.core.today/v1/reseller/customers/user_abc123/reactivate \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"customer_id": "user_abc123",
"status": "active"
}참고: 존재하지 않거나 삭제된 고객에 대해서는 두 엔드포인트 모두 404를 반환합니다. 소프트 삭제된 고객은 재개할 수 없습니다 (404) — 새 고객을 생성하세요.
월간 예산 / Monthly Budgets
구독형 재판매를 위해 고객에게 월간 예산(monthly_allowance, 0 초과 최대 1,000,000)을 설정할 수 있습니다. spent는 계속 누적되며(정산 검증/reconcile 의미론 불변), 매월 1일 00:00 UTC 경계가 지난 뒤 잔여 예산을 초과하는 첫 차감 시점에 lazy로 cap이 max(cap, spent + allowance)로 올라갑니다 — 고객의 잔여 예산이 매월 최소 allowance만큼 보장됩니다.
동작 규칙
- • 미사용 topup 잔여(allowance를 초과하는 잔액)는 보존됩니다. 단, allowance 자체는 이월/누적되지 않습니다 — 여러 달 미사용 후에도 1회만 적용됩니다.
- • 예산 설정은 즉시 크레딧을 지급하지 않습니다. 즉시 지급이 필요하면
topup을 사용하세요. - •
reset_due: true는 월 경계가 지났고, 해당 고객의 잔여 예산이 부족해지는 다음 차감에서 자동 반영된다는 표시입니다. 잔여 예산 내 차감은 리셋 없이 그대로 진행됩니다 (cap은 증가만 하므로 결과는 동일). - • 지갑 조회(get/list) 응답에
monthly_allowance,next_reset_at,reset_due3개 필드가 포함됩니다.
docs.endCustomerBudgets.step8.code동작 예시: cap 1,500 / spent 1,400 (잔여 100)인 고객이 reset_due: true 상태라면, 잔여 100을 초과하는 차감이 들어오는 시점에 cap이 max(1500, 1400 + 500) = 1900으로 올라가 잔여가 500으로 회복된 뒤 차감됩니다. spent는 리셋되지 않으므로 정산·reconcile 집계는 그대로 유지됩니다.
참고: 존재하지 않는 고객에 대한 PUT /reseller/customers/{id}/budget은 404를 반환합니다. 월간 리셋은 cap을 올리므로 시간이 지나며 committed_cap이 자동으로 증가합니다 — 월간 예산 고객이 많다면 워크스페이스 풀 잔액과 overcommit 상태(Step 2)를 함께 모니터링하세요.
사용 내역, 집계, 정산 CSV
고객별 원시 사용 내역(raw records)과 기간 집계(summary)를 직접 조회할 수 있어 별도의 사용량 저장소를 구축할 필요가 없습니다. 기간은 start_date / end_date (ISO 8601)로 지정합니다.
# 원시 사용 내역 (limit 1..1000, 기본 100, 최신순)
curl "https://api.core.today/v1/reseller/customers/user_abc123/usage?limit=20" \
-H "X-API-Key: cdt_your_api_key"
# Response — usage 로그 원본 레코드 (주요 필드)
{
"records": [
{
"timestamp": "2026-06-30T11:00:00Z",
"provider": "black-forest-labs",
"model": "flux-schnell",
"credits_used": 10,
"final_status": "completed",
"job_id": "job_xxx",
...
}
]
}
# 특정 고객의 기간 집계
curl "https://api.core.today/v1/reseller/customers/user_abc123/usage/summary?start_date=2026-06-01T00:00:00Z&end_date=2026-07-01T00:00:00Z" \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"customer_id": "user_abc123",
"total_credits": 42.5,
"total_requests": 3,
"failed_requests": 1,
"by_model": [
{ "model": "flux-schnell", "credits": 42.5, "count": 3 }
]
}
# 워크스페이스 전체 고객 집계 (rollup)
curl "https://api.core.today/v1/reseller/usage/summary?start_date=2026-06-01T00:00:00Z&end_date=2026-07-01T00:00:00Z" \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"customers": [
{ "customer_id": "user_abc123", "credits": 42.5, "count": 3, "failed": 1 }
],
"total_credits": 42.5,
"total_requests": 3,
"failed_requests": 1
}
# 정산용 CSV 다운로드 (헤더: customer_id,credits,requests,failed_requests)
curl "https://api.core.today/v1/reseller/usage/summary?format=csv" \
-H "X-API-Key: cdt_your_api_key" \
-o billing.csv
# 일 단위 breakdown (group_by=day) — 두 summary 엔드포인트 모두 지원 (JSON 전용)
curl "https://api.core.today/v1/reseller/usage/summary?start_date=2026-06-01T00:00:00Z&end_date=2026-07-01T00:00:00Z&group_by=day" \
-H "X-API-Key: cdt_your_api_key"
# Response에 by_day 배열이 추가됨
{
"customers": [...],
"total_credits": 42.5,
"total_requests": 3,
"failed_requests": 1,
"by_day": [
{ "date": "2026-06-01", "credits": 12.5, "count": 1, "failed": 0 },
{ "date": "2026-06-02", "credits": 30.0, "count": 2, "failed": 1 }
]
}정산 기준 (invoice-grade): total_requests와 count는 성공한 요청만 집계합니다. 실패·취소된 요청은 크레딧이 0이며 failed_requests / failed로 별도 보고됩니다 — 에러가 난 요청이 고객에게 청구되는 일은 없습니다. group_by=day의 by_day 배열은 명세서(statement) 라인 아이템으로 바로 사용할 수 있습니다.
정산 검증 / Reconcile
고객별 예산 미터(meter)와 usage 로그가 일치하는지 셀프서브로 검증할 수 있습니다. 미터는 한도 enforcement용 advisory(참고용, 재구축 가능) 값이며, 정산·감사 기준은 usage 로그입니다. 허용 오차(tolerance)는 0.01 크레딧이며, 오차 이내면 in_sync: true로 반환됩니다.
# 단일 고객 검증
curl https://api.core.today/v1/reseller/customers/user_abc123/reconcile \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"customer_id": "user_abc123",
"meter_spent": 450.0,
"usage_credits": 450.0,
"drift": 0.0,
"in_sync": true
}
# 워크스페이스 전체 검증 (limit 1..1000, 기본 500)
curl "https://api.core.today/v1/reseller/reconcile?limit=1000" \
-H "X-API-Key: cdt_your_api_key"
# Response — drift가 있는 고객만 반환
{
"checked": 42,
"in_sync": 41,
"drifted": [
{
"customer_id": "user_xyz",
"meter_spent": 120.0,
"usage_credits": 100.0,
"drift": 20.0
}
],
"truncated": false
}운영 팁: 월별 정산 전에 워크스페이스 reconcile을 한 번 돌려 drifted가 비어 있는지 확인하세요. truncated: true면 검사되지 않은 고객이 남아 있으므로 limit을 올려 다시 호출하세요. drift가 발견되면 support@core.today로 문의하세요 — usage 로그 기준으로 미터를 재구축해 드립니다.
2026-07-02 이전 LLM 트래픽 주의: 2026-07-02 이전의 LLM/chat 사용량은 미터에는 반영되어 있지만 usage 로그에는 없습니다. 해당 기간에 LLM 트래픽이 있었던 고객은 양(+)의 drift가 정상적으로 나타날 수 있습니다.
고객별 파일 조회
특정 고객이 생성/업로드한 파일(예측 결과 + 업로드 입력)을 신선한 다운로드 URL과 함께 조회합니다. team_id는 API 키에서, customer_id는 경로에서 강제되므로 다른 고객의 파일이 섞일 수 없습니다. 콘솔에서는 GET /teams/{team_id}/customers/{customer_id}/files(Clerk JWT)로 동일하게 조회할 수 있습니다.
curl "https://api.core.today/v1/reseller/customers/user_abc123/files?limit=20" \
-H "X-API-Key: cdt_your_api_key"
# Response
{
"files": [
{
"object_key": "aiapi/team123/outputs/1734175200000_result.png",
"filename": "result.png",
"folder": "outputs",
"content_type": "image/png",
"file_size": 1048576,
"uploaded_at": "2026-07-10T11:00:00Z",
"job_id": "job_xxx",
"source": "ai_output",
"url": "https://files.core.today/aiapi/...&Expires=...",
"expires_at": "2026-07-17T11:00:00Z"
}
],
"next_cursor": null
}folder, content_type, source, start_date/end_date로 필터링하고, cursor로 페이지네이션합니다. 각 파일의 다운로드 URL은 url_expiration으로 조정할 수 있으며 최대 7일로 캡됩니다 — 더 긴 재접근이 필요하면 File Upload 문서의 POST /files/sign을 사용하세요.
Phantom 토큰 — 최종 고객 직접 파일 접근
Phantom 토큰은 최종 고객이 리셀러 서버를 거치지 않고 브라우저나 앱에서 자기 파일을 직접 조회·재서명하도록 하는 짧은 수명의 scoped 토큰입니다. 리셀러 서버가 발급해 고객에게 전달하면, 고객은 X-Phantom-Token헤더로 게이트웨이를 직접 호출합니다. 리셀러의 실제 API 키는 브라우저에 노출되지 않습니다.
흐름
- 1. 리셀러 서버가
POST /reseller/customers/{id}/token으로 토큰을 발급합니다 (X-API-Key 인증). - 2. 발급된 토큰을 최종 고객(브라우저/앱)에게 전달합니다.
- 3. 고객이
X-Phantom-Token헤더로GET /reseller/customers/{id}/files·POST /files/sign를 직접 호출합니다.
토큰 특성
- • TTL — 기본 15분(900초), 최소 5분(300초), 최대 60분(3600초). 범위 밖 ttl_seconds는 400. 만료가 토큰에 내장됩니다.
- • 스코프 —
files:list/files:sign만 부여 가능하며 파일 전용입니다(예측 생성 불가 → 예산 소비 없음). - • 브라우저 CORS — 두 소비 엔드포인트는 origin *(비자격 증명)를 허용하므로 브라우저에서 직접 호출할 수 있습니다.
- • 소프트 철회 — 지갑이 suspend되거나 삭제되면 발급된 토큰이 즉시 거부됩니다.
- • 명시적 철회 — 유출된 토큰 하나를 TTL 만료 전에 즉시 무효화하려면
POST /reseller/customers/{id}/token/revoke를 호출하세요(고객 전체를 정지할 필요가 없습니다). - • 전체 철회 — 토큰이 유출됐는데 원문을 모른다면
POST /reseller/customers/{id}/token/revoke-all로 해당 고객의 미만료 토큰 전부를 한 번에 무효화하세요. 지갑의 token_epoch를 원자적으로 +1 하여 이전 발급분 전체가 검증에서 거부됩니다 — 토큰 원문이 필요 없고, suspend와 달리 서비스 중단 없이 새 토큰을 바로 재발급할 수 있습니다. - • 철회 실패는 명시적 에러로 응답합니다 — 철회 기능 도입 전에 발급된 구형 토큰은
409 token_not_revocable(지갑 정지로 대체), 저장소 일시 장애 시503 revocation_unavailable가 반환되며 이때 토큰은 아직 유효하므로 재시도하세요. - • 보안 — 리셀러의 실제 API 키는 고객에게 전달되지 않습니다.
유출 사고 대응 절차
- 1. 유출된 토큰 원문을 알고 있다면
POST .../token/revoke로 해당 토큰만 정밀 폐기합니다 — 다른 토큰은 계속 유효합니다. - 2. 원문을 모른다면
POST .../token/revoke-all로 고객의 모든 토큰을 무효화합니다 — 서비스는 계속되며 새 토큰을 즉시 재발급하면 됩니다. - 3. 고객 계정 자체가 침해된 극단적 상황이면
POST .../suspend로 지갑을 정지하세요 — 모든 토큰이 자동으로 전체 무효화되고, 재활성화 전까지 소비(서비스)도 함께 중단됩니다.
# 1) Reseller server mints a token (X-API-Key)
curl -X POST https://api.core.today/v1/reseller/customers/user_abc123/token \
-H "Content-Type: application/json" \
-H "X-API-Key: cdt_your_api_key" \
-d '{ "ttl_seconds": 900, "scope": ["files:list", "files:sign"] }'
# Response
{
"token": "eyJ2IjoxLC4uLn0.c2ln",
"expires_at": "2026-07-13T00:15:00+00:00",
"scope": ["files:list", "files:sign"],
"customer_id": "user_abc123"
}
# 2) End-customer (browser/app) calls the gateway directly — no API key
curl https://api.core.today/v1/reseller/customers/user_abc123/files \
-H "X-Phantom-Token: eyJ2IjoxLC4uLn0.c2ln"
curl -X POST https://api.core.today/v1/files/sign \
-H "Content-Type: application/json" \
-H "X-Phantom-Token: eyJ2IjoxLC4uLn0.c2ln" \
-d '{ "object_key": "aiapi/team123/outputs/1734175200000_result.png" }'
# 3) Leak response without the token plaintext: kill every outstanding token
curl -X POST https://api.core.today/v1/reseller/customers/user_abc123/token/revoke-all \
-H "X-API-Key: cdt_your_api_key"
# → { "revoked": true, "customer_id": "user_abc123", "token_epoch": 3 }
# --- Node SDK: mint from the reseller server ---
const { token, expires_at } = await client.reseller.mintCustomerToken(
"user_abc123",
{ ttlSeconds: 900, scope: ["files:list", "files:sign"] },
);Webhook 알림
기존 webhook 설정(콘솔 /webhooks 또는 API)에서 아래 이벤트를 구독하면 고객 예산과 워크스페이스 풀 상태를 폴링 없이 모니터링할 수 있습니다.
| 이벤트 | 발생 조건 | Payload |
|---|---|---|
| customer.balance_low | 고객의 잔여 예산이 cap의 10% 이하로 떨어짐 | team_id, customer_id, remaining, cap, spent, threshold |
| customer.cap_reached | 예산 초과로 차감(charge)이 거부됨 | team_id, customer_id, cap, spent |
| workspace.pool_low | 워크스페이스 공유 풀이 임계값 아래로 떨어짐 — 기본 100 크레딧, 팀별 override: settings.pool_low_threshold | team_id, remaining_credits, threshold |
중복 발송 방지(dedup): 같은 상태로 반복 발송되지 않습니다 (6시간 윈도우). top-up 또는 월간 예산 리셋으로 잔액이 회복되면 알림이 다시 활성화(re-arm)됩니다.
레이트리밋
아래의 무거운 집계/파일 엔드포인트에는 API 키의 일반 한도와 별도로 더 엄격한 전용 예산이 적용됩니다:
- •
GET /reseller/customers/{id}/files - •
GET /reseller/customers/{id}/usage/summary - •
GET /reseller/usage/summary - •
GET /reseller/customers/{id}/reconcile - •
GET /reseller/reconcile
전용 예산은 10/min · 200/hour · 2,000/day입니다. 초과 시 429가 반환되며, 응답의 Retry-After헤더(초)만큼 기다린 후 재시도하세요.
대량 정산 팁: 고객별로 summary를 반복 호출하지 말고, 워크스페이스 전체 롤업 GET /reseller/usage/summary 1회 호출로 모든 고객의 청구 데이터를 한 번에 가져오세요.
에러 코드
과금 경로(prediction/LLM)의 에러 응답에는 사람이 읽는 detail 외에 machine-readable error.code 필드가 포함됩니다 (402 에러에는 error.required / error.available / error.customer_id도 포함). 재시도·충전 로직은 detail 문자열이 아닌 error.code로 분기하세요.
| HTTP | 코드 | 설명 및 조치 |
|---|---|---|
| 400 | invalid_customer_id | X-Customer-Id 누락/형식 오류 (과금 경로, 리셀러 모드에서 헤더 필수). 관리 엔드포인트에서는 customer_id 형식 오류·잘못된 커서가 detail 메시지와 함께 400으로 반환됩니다. |
| 402 | customer_not_found | 고객 미등록 (과금 경로) → POST /reseller/customers로 먼저 생성 |
| 402 | customer_wallet_insufficient | 고객 예산 소진 (과금 경로) → top-up으로 충전 |
| 402 | customer_suspended | 고객이 일시 정지됨 (과금 경로) → POST /reseller/customers/{id}/reactivate로 재개 |
| 402 | reseller_account_insufficient | 워크스페이스 풀 소진 (과금 경로) → 충전 코드로 워크스페이스 크레딧 충전 |
| 403 | (detail message) | "Reseller mode is not enabled for this workspace" — 관리 엔드포인트(/reseller/customers* 등)는 리셀러 모드가 활성화된 워크스페이스에서만 사용 가능 ( GET /reseller/status는 예외) |
| 404 | (detail message) | "Customer not found" — 조회/충전/예산/정지/재개 대상 고객이 없거나 소프트 삭제됨 |
| 409 | (detail message) | "Customer wallet already exists" — 같은 customer_id가 이미 존재함 (bulk에서는 항목별 wallet_exists에러로 보고) |
| 429 | (detail message) | "Customer limit exceeded for this team" — 팀당 최대 고객 수(10,000) 초과 |
SDK로 사용하기
공식 Node.js/Python SDK에 reseller 네임스페이스가 내장되어 있어 위의 모든 엔드포인트를 타입과 함께 호출할 수 있습니다. prediction 호출에는 customerId / customer_id 옵션으로 X-Customer-Id 헤더가 자동 설정됩니다(클라이언트 기본값 또는 요청별 지정).
// Node.js — npm install @coredot/aiapi
import { AIAPI } from "@coredot/aiapi";
const client = new AIAPI({ apiKey: "cdt_your_api_key" });
await client.reseller.createCustomer({ customer_id: "user_abc123", cap: 1000 });
await client.predictions.create({
model: "black-forest-labs/flux-schnell",
input: { prompt: "hello" },
customerId: "user_abc123",
});
# Python — pip install coredot-aiapi
from aiapi import AIAPI
client = AIAPI(api_key="cdt_your_api_key")
client.reseller.create_customer(customer_id="user_abc123", cap=1000)
client.predictions.create(
model="black-forest-labs/flux-schnell",
input={"prompt": "hello"},
customer_id="user_abc123",
)워크플로우 요약
- 1. 콘솔 고객(Customers) 페이지에서 "리셀러 모드 신청" → 운영팀 승인
- 2.
GET /reseller/status—reseller_mode: true확인 및 워크스페이스 크레딧 잔액 점검 - 3.
POST /reseller/customers— 고객 등록 및 초기 cap 설정 (대량이면/bulk) - 4. 모든 AI 요청(prediction + LLM)에
X-Customer-Id헤더 포함 (리셀러 모드에서 필수) - 5. 고객 예산 소진 시
POST /reseller/customers/{id}/topup으로 충전, 구독형이면 월간 예산(monthly_allowance) 설정 - 6. 미납 고객은
POST /reseller/customers/{id}/suspend로 즉시 차단, 결제 확인 후reactivate로 재개 - 7. 정산 시
GET /reseller/usage/summary?format=csv로 청구용 CSV 다운로드 - 8. 월별 정산 전
GET /reseller/reconcile로 미터와 usage 로그 일치 확인
워크스페이스 콘솔에서 관리하기
고객 예산은 API 외에도 워크스페이스 콘솔에서 직접 관리할 수 있습니다. 워크스페이스 → 고객(Customers) 페이지에서 고객 생성, 목록 조회, 충전, 월간 예산 설정, 일시 정지/재개, 사용 내역 확인, 삭제를 UI로 수행할 수 있습니다. 콘솔은 워크스페이스 로그인(Clerk)을 사용하며, /reseller/* API는 API 키( X-API-Key)를 사용합니다.