Skip to main content
Core.Today
가격

API Reference

Core.Today API 전체 엔드포인트 레퍼런스

Base URLs

Image/Video:https://api.core.today/v1
LLM (OpenAI):https://api.core.today/llm/openai/v1
LLM (Anthropic):https://api.core.today/llm/anthropic/v1
LLM (Gemini):https://api.core.today/llm/gemini/v1beta

Authentication

모든 API 요청에는 인증이 필요합니다. 다음 방법 중 하나를 사용하세요:

# X-API-Key 헤더 (권장)
curl -H "X-API-Key: cdt_your_api_key" ...

# Authorization 헤더
curl -H "Authorization: Bearer cdt_your_api_key" ...

Models & Pricing

사용 가능한 모델 목록, 입력 파라미터 스키마, 크레딧 가격을 프로그래밍 방식으로 조회할 수 있습니다. 아래 엔드포인트는 모두 인증 없이 호출 가능합니다.

GET/providers/models/catalog

전체 활성 모델 카탈로그 — 크레딧 가격, 차등 가격 룰, 카테고리 포함

Query Parameters

ParameterTypeRequiredDescription
categorystringNo카테고리 필터 (예: image, video, audio, chat)
model_typestringNo모델 타입 필터 (예: image, video, llm)
searchstringNo모델명/설명 검색어
featured_onlybooleanNo추천 모델만 (기본 false)
include_llmbooleanNoLLM 게이트웨이 모델 포함 여부 (기본 true)

Example

curl "https://api.core.today/v1/providers/models/catalog?category=video"

Response

{
  "models": [
    {
      "model_id": "black-forest-labs/flux-schnell",
      "provider_id": "replicate",
      "name": "FLUX.1 Schnell",
      "model_type": "image",
      "category": "image_generation",
      "credits": 7,
      "display_credits": 7,        // 기본 구성 1회 호출 시 실제 차감 크레딧
      "pricing_type": "fixed",
      "pricing_rules": null,        // input_based 모델은 해상도/길이별 룰 포함
      "tags": ["replicate", "image"]
    }
  ],
  "total": 248,
  "filters": { "categories": ["..."], "model_types": ["..."] }
}

가격 계산

display_credits는 기본 구성 1회 호출의 실제 차감 크레딧입니다. 해상도·영상 길이 등 입력에 따라 가격이 달라지는 모델(pricing_type: input_based)은 pricing_rules의 룰이 적용되며, 호출 전 정확한 견적은 POST /predictions/estimate로 확인할 수 있습니다.

GET/providers/llm/models

LLM 게이트웨이 모델 목록 — 토큰당 크레딧 요율 포함

Example

curl "https://api.core.today/v1/providers/llm/models"

Response

{
  "models": [
    {
      "id": "claude-sonnet-4-5",
      "provider": "anthropic",
      "category": "chat",
      "pricing": {
        "input_per_token": 0.005574,   // 크레딧/토큰 (1M 토큰당 5,574 크레딧)
        "output_per_token": 0.02787
      },
      "is_active": true
    }
  ]
}

LLM 과금 방식

LLM은 실제 사용한 입력/출력 토큰 수 × 토큰당 크레딧 요율로 과금됩니다. 각 모델의 컨텍스트 길이·파라미터·예제는 LLM 문서에서 확인하세요.

GET/providers

프로바이더 목록과 대략적인 크레딧 범위

curl "https://api.core.today/v1/providers"
GET/llms.txt

AI 에이전트용 기계 판독 모델 문서 (plain text)

전체 모델 목록과 모델별 상세 스펙(파라미터·가격·예제)을 LLM이 읽기 좋은 플레인 텍스트로 제공합니다. AI 코딩 도구에 그대로 붙여넣어 통합 코드를 생성할 때 유용합니다.

# 전체 모델 목록
curl "https://api.core.today/v1/llms.txt"

# 모델별 상세 문서 (파라미터 스키마 + 가격 + 예제)
curl "https://api.core.today/v1/models/black-forest-labs/flux-schnell/llms.txt"

# 카테고리별 가이드 (문서 사이트 기반)
curl "https://console.core.today/llms.txt"
curl "https://console.core.today/llms/llm.txt"

Predictions

POST/v1/predictions

AI 모델로 예측(이미지/비디오/오디오 생성) 요청을 생성합니다.

Request Body

ParameterTypeRequiredDescription
modelstringYes모델 ID (예: black-forest-labs/flux-schnell). provider/model_id 형식도 지원 (예: replicate/black-forest-labs/flux-schnell)
inputobjectYes모델별 입력 파라미터
is_publicbooleanNotrue면 영구 public URL 생성 (기본: false)
output_folderstringNo결과물 저장 폴더 경로
webhookstringNo완료 시 호출할 Webhook URL
webhook_eventsstring[]NoWebhook 이벤트 타입 (completed, failed)

Example Request

curl -X POST https://api.core.today/v1/predictions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $CORE_API_KEY" \
  -d '{
    "model": "black-forest-labs/flux-schnell",
    "input": {
      "prompt": "A beautiful sunset over mountains",
      "aspect_ratio": "16:9"
    }
  }'

Response

{
  "job_id": "pred_abc123xyz",
  "status": "pending",
  "provider": "replicate",
  "model": "black-forest-labs/flux-schnell",
  "created_at": "2024-12-28T12:00:00Z"
}
POST/v1/predictions/upload

파일을 함께 업로드하면서 예측 요청을 생성합니다 (multipart/form-data).

언제 사용하나요?

파일 업로드와 예측 요청을 한 번에 처리합니다. 별도로 presigned URL을 발급받아 S3에 업로드할 필요가 없습니다.

Form Fields

ParameterTypeRequiredDescription
modelstringYes모델 ID (예: black-forest-labs/flux-schnell)
inputstring (JSON)No모델 입력 파라미터 JSON 문자열 (기본: "{}")
is_publicstringNo"true" 또는 "false" (기본: "false")
output_folderstringNo결과물 저장 폴더 경로
webhook_urlstringNo완료 시 호출할 Webhook URL

File Fields

file:<input_key> 패턴으로 파일을 업로드합니다. 파일은 자동으로 S3에 업로드되고, 생성된 URL이 input[input_key]에 주입됩니다.

ParameterTypeRequiredDescription
file:<input_key>fileNo업로드할 파일. 같은 키로 여러 파일 전송 시 배열로 처리됨 (최대 50MB/파일)

Example Request

# 단일 파일 업로드
curl -X POST https://api.core.today/v1/predictions/upload \
  -H "X-API-Key: $CORE_API_KEY" \
  -F "model=flux-kontext-pro" \
  -F 'input={"prompt":"change background to a beach sunset"}' \
  -F "file:image_url=@photo.png"

# 복수 파일 업로드
curl -X POST https://api.core.today/v1/predictions/upload \
  -H "X-API-Key: $CORE_API_KEY" \
  -F "model=flux-kontext-pro" \
  -F 'input={"prompt":"combine these images"}' \
  -F "file:image_url=@photo1.png" \
  -F "file:image_url=@photo2.png"

Response

{
  "job_id": "pred_abc123xyz",
  "status": "pending",
  "provider": "replicate",
  "model": "black-forest-labs/flux-kontext-pro"
}

제한 사항

  • 파일당 최대 크기: 50MB
  • 응답 형식은 POST /predictions와 동일합니다
  • 업로드된 파일은 S3 스토리지에 저장되며, 7일간 접근 가능한 URL이 생성됩니다
GET/v1/predictions/:id

예측 작업의 상태와 결과를 조회합니다.

Path Parameters

ParameterTypeRequiredDescription
idstringYes예측 작업 ID

Response (completed)

{
  "job_id": "pred_abc123xyz",
  "status": "completed",
  "provider": "replicate",
  "model": "black-forest-labs/flux-schnell",
  "result": [
    "https://files.core.today/aiapi/9f3a/pred_abc123xyz/o/0.png?Expires=...&Signature=..."
  ],
  "public_url": null,
  "output_files": [
    {
      "object_key": "aiapi/9f3a/pred_abc123xyz/o/0.png",
      "url": "https://files.core.today/aiapi/9f3a/pred_abc123xyz/o/0.png?Expires=...&Signature=..."
    }
  ],
  "created_at": "2024-12-28T12:00:00Z"
}

output_files — 영구 참조를 저장하세요

result의 URL과 output_files[].url은 모두 7일 뒤 만료되는 presigned URL입니다. 반면 output_files[].object_key(예 aiapi/<hash>/<job_id>/o/0.png)는 파일이 존재하는 한 변하지 않는 영구 키입니다. URL이 아니라 object_key를 저장해 두고, 나중에 POST /files/sign으로 새 URL을 재발급받으세요. 같은 object_key/URL은 다음 예측의 input에 그대로 넣어 체이닝할 수도 있습니다(자동 재서명).

Status Values

  • pending - 대기 중
  • processing - 처리 중
  • completed - 성공
  • failed - 실패
  • cancelled - 취소됨
DELETE/v1/predictions/:id

진행 중인 예측 작업을 취소합니다.

Path Parameters

ParameterTypeRequiredDescription
idstringYes취소할 예측 작업 ID

Response

{
  "message": "Job cancelled",
  "credits_refunded": 12.5
}
POST/v1/predictions/estimate

생성 전 크레딧 견적 — 부작용 없음 (차감/작업 생성 안 함)

Request

curl -X POST "https://api.core.today/v1/predictions/estimate" \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kwaivgi/kling-v2.6",
    "input": { "prompt": "...", "duration": 10 }
  }'

Response

{
  "model": "kwaivgi/kling-v2.6",
  "credits": 3260,
  "exact": true,      // false면 입력만으로 확정 불가(실행 후 확정)
  "note": null
}

차등 가격 모델에 유용

해상도·영상 길이·출력 수에 따라 가격이 달라지는 모델은 실행 전 이 엔드포인트로 정확한 차감액을 확인하세요.

Files

POST/v1/files/upload-url

S3 presigned upload URL을 발급받습니다.

Request Body

ParameterTypeRequiredDescription
filenamestringYes업로드할 파일 이름
content_typestringNoMIME 타입 (기본: application/octet-stream)
folderstringNo저장 폴더 경로

Example Request

curl -X POST https://api.core.today/v1/files/upload-url \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $CORE_API_KEY" \
  -d '{
    "filename": "input.jpg",
    "content_type": "image/jpeg",
    "folder": "my-project/inputs"
  }'

Response

{
  "upload_url": "https://s3.amazonaws.com/bucket/...",
  "file_url": "https://cdn.core.today/files/abc123/input.jpg",
  "expires_at": "2024-12-28T13:00:00Z"
}

파일 업로드 방법

발급받은 upload_url로 PUT 요청을 보내 파일을 업로드하세요:

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @input.jpg
GET/v1/files

업로드한 파일 목록을 조회합니다.

Query Parameters

ParameterTypeRequiredDescription
folderstringNo폴더 경로 필터
limitintegerNo반환할 최대 개수 (기본: 20)
cursorstringNo페이지네이션 커서
POST/v1/files/sign

이미 소유한 객체의 다운로드 URL을 새로 발급합니다. 파일 자체는 삭제되지 않으니 URL이 만료돼도 재서명하면 됩니다.

Request Body

ParameterTypeRequiredDescription
object_keystringNo재서명할 S3 객체 키
urlstringNo이전에 발급된 CloudFront URL
job_idstringNoprediction job ID — 해당 작업의 출력 파일 전부를 한 번에 재서명
expirationintegerNo요청 만료 시간(초). 최대 7일(604,800)로 캡

object_key, url, job_id 정확히 하나만 지정합니다.

Example Request

curl -X POST https://api.core.today/v1/files/sign \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $CORE_API_KEY" \
  -d '{"object_key": "aiapi/team123/outputs/1734175200000_result.png"}'

Response

{
  "files": [
    {
      "object_key": "aiapi/team123/outputs/1734175200000_result.png",
      "url": "https://files.core.today/aiapi/...&Expires=...&Signature=...",
      "expires_at": "2024-12-21T10:00:00Z",
      "content_type": "image/png",
      "file_size": 1048576
    }
  ]
}

object_key를 저장하세요

다운로드 URL은 만료돼도 파일 보관 기간(팀 플랜별 free 30일 / pro 365일 / team·enterprise 무기한) 동안은 그대로입니다. URL 대신 object_key를 저장해 두면 언제든 이 엔드포인트로 새 URL을 재발급받을 수 있습니다. 다른 팀(또는 리셀러의 다른 고객) 소유 객체는 존재 여부와 무관하게 동일한 404 file_not_found로 응답합니다.

Databases

POST/v1/databases

새 데이터베이스를 생성합니다.

Request Body

ParameterTypeRequiredDescription
namestringYes데이터베이스 이름
descriptionstringNo설명
schemaobjectYes필드 스키마 정의

Example Request

{
  "name": "products",
  "description": "E-commerce product catalog",
  "schema": {
    "title": { "type": "text" },
    "description": { "type": "text" },
    "price": { "type": "float" },
    "category": { "type": "keyword" },
    "embedding": { "type": "knn_vector", "dimension": 768 }
  }
}
POST/v1/databases/:uid/search

문서를 검색합니다.

Request Body

ParameterTypeRequiredDescription
queryobjectYesOpenSearch Query DSL
sizeintegerNo반환할 결과 수 (기본: 10)
fromintegerNo오프셋 (페이지네이션)

Example: 전문 검색

{
  "query": {
    "multi_match": {
      "query": "wireless headphones",
      "fields": ["title", "description"]
    }
  },
  "size": 20
}

Example: 벡터 검색 (kNN)

{
  "query": {
    "knn": {
      "embedding": {
        "vector": [0.1, 0.2, ...],
        "k": 10
      }
    }
  }
}

End-Customer Budgets (Reseller API)

리셀러 워크스페이스에서 end-customer별 크레딧 예산을 관리하는 엔드포인트입니다. 기본 통합 표면은 X-API-Key로 인증하는 /v1/reseller/* API로, 리셀러 백엔드 서버에서 직접 호출하도록 설계되었습니다. 콘솔(사람) 용으로는 동일 기능의 Clerk-JWT 표면 /v1/teams/{teamId}/customers/*가 별도로 제공됩니다 (owner/admin 역할 필요, 응답 형식 동일). GET /reseller/status를 제외한 모든 엔드포인트는 워크스페이스에 reseller_mode가 활성화되어 있어야 하며, 비활성 시 403이 반환됩니다. 개념과 상세 시맨틱은 End-Customer Budgets 가이드를 참조하세요.

X-Customer-Id 헤더 (예측/LLM 요청)

리셀러 워크스페이스에서 /predictions 또는 LLM 엔드포인트로 요청을 보낼 때 X-API-Key와 함께 X-Customer-Id: <customer_id> 헤더를 포함하면 해당 고객의 예산에서 크레딧이 차감됩니다. 오류 코드: 헤더 누락/형식 오류 시 400 invalid_customer_id, 미등록 고객 402 customer_not_found, 고객 예산 소진 402 customer_wallet_insufficient, 정지된 고객 402 customer_suspended, 워크스페이스 풀 부족 402 reseller_account_insufficient. LLM 게이트웨이의 크레딧 예약도 동일한 코드로 실패 사유를 분류하므로, “크레딧 부족” 대신 코드를 그대로 노출하세요 — 고객 지갑 단계와 워크스페이스 풀 단계는 해결 방법이 정반대입니다(고객 충전 vs 워크스페이스 충전). customer_not_found PATCH /reseller/settingsdefault_customer_cap으로 자동 지갑 생성(auto-provision)을 켜면 방지할 수 있습니다.

상태

GET/v1/reseller/status

리셀러 모드 여부와 워크스페이스 크레딧/커밋 현황을 조회합니다. 유일하게 reseller_mode 게이트가 없는 엔드포인트.

Response

{
  "reseller_mode": true,
  "customer_count": 42,
  "max_customers": 10000,
  "workspace_credits": 50000.0,
  "committed_cap": 62000.0,
  "overcommit_ratio": 1.24,
  "overcommitted": true
}

committed_cap은 활성 고객 cap의 합계. overcommit_ratio > 1.0이면 고객 예산 합이 워크스페이스 풀을 초과하여, 전원 소진 시 일부 고객이 reseller_account_insufficient를 만날 수 있습니다.

워크스페이스 설정

GET/v1/reseller/settings

워크스페이스 설정(지갑 자동 생성 cap, pool_low 웹훅 임계값)을 조회합니다.

Response

{
  "default_customer_cap": 25,
  "pool_low_threshold": 500,
  "auto_provision_enabled": true
}

auto_provision_enabled은 파생값 — default_customer_cap이 설정되어 있으면 true입니다.

PATCH/v1/reseller/settings

워크스페이스 설정을 부분 갱신합니다. 생략한 필드는 유지, 명시적 null은 해제. 정의되지 않은 필드는 422.

Request Body

ParameterTypeRequiredDescription
default_customer_capnumber | nullNo지갑 자동 생성(auto-provision) cap (0~1,000,000). 설정하면 미등록 X-Customer-Id의 첫 요청에서 이 cap으로 지갑을 만들고 요청을 진행합니다 (402 customer_not_found 대신). 0도 유효 — 지갑만 만들고 예산은 topup으로 부여. null이면 자동 생성 비활성.
pool_low_thresholdnumber | nullNoworkspace.pool_low 웹훅이 발생하는 워크스페이스 잔액 임계값 (기본 100). null이면 기본값으로 복귀.

Response

{
  "default_customer_cap": 25,
  "pool_low_threshold": 500,
  "auto_provision_enabled": true
}

auto-provision은 거절 경로에서만 동작하므로 정상 트래픽에 추가 비용이 없습니다. 가입 훅이 누락·실패해도 사용자의 첫 생성이 402로 죽지 않도록 하는 안전망으로 쓰세요. 소프트 삭제된 지갑은 트래픽만으로 부활하지 않습니다 (명시적 restore 필요).

고객 관리

POST/v1/reseller/customers

새 고객 예산(지갑)을 생성합니다. 201 Created 반환. 이미 존재하면 409, 팀당 한도(10,000) 초과 시 429.

Request Body

ParameterTypeRequiredDescription
customer_idstringYes고객 식별자. 허용 문자: [A-Za-z0-9_.-], 최대 128자. PII(이메일 등) 사용 금지.
capnumberYes초기 크레딧 한도 (≥ 0)
external_refstringNo외부 참조/메모 (선택, 최대 512자)
namestringNo사람이 읽는 표시 이름 (선택, 최대 200자). 콘솔 고객 목록에 표시됨.
monthly_allowancenumberNo월간 반복 예산 (> 0). 매월 경계(UTC)에 remaining이 최소 이 값이 되도록 cap이 올라감. spent는 누적 유지.

Response (201)

{
  "customer_id": "user_abc123",
  "cap": 2000,
  "spent": 0,
  "remaining": 2000,
  "status": "active",
  "name": null,
  "external_ref": null,
  "monthly_allowance": 1000,
  "next_reset_at": "2026-08-01T00:00:00+00:00",
  "reset_due": false,
  "created_at": "2026-07-01T09:00:00Z",
  "updated_at": "2026-07-01T09:00:00Z"
}

monthly_allowance / next_reset_at은 월간 예산을 지정한 경우에만 포함됩니다. 지갑은 생성됐지만 예산 기록에 실패하면 "budget_error": "budget_set_failed"가 포함되며 PUT .../budget으로 재시도하세요.

POST/v1/reseller/customers/bulk

최대 100명의 고객을 한 번에 생성합니다 (온보딩용). 항목별 독립 처리 — 부분 성공.

Request Body

ParameterTypeRequiredDescription
customersobject[]Yes생성할 고객 배열 (1~100개). 각 항목은 POST /reseller/customers의 body와 동일.

Response

{
  "created": [
    { "customer_id": "user_abc123", "cap": 2000, "spent": 0, "remaining": 2000, "status": "active" }
  ],
  "errors": [
    { "customer_id": "user_dup", "error": "wallet_exists" }
  ]
}
GET/v1/reseller/customers

고객 지갑 목록을 조회합니다 (커서 기반 페이지네이션).

Query Parameters

ParameterTypeRequiredDescription
cursorstringNo이전 응답의 next_cursor 값. 잘못된 값이면 400.
limitintegerNo페이지 크기 (1~500, 기본: 100)

Response

{
  "items": [
    {
      "customer_id": "user_abc123",
      "cap": 2000,
      "spent": 450,
      "remaining": 1550,
      "status": "active",
      "name": "Acme Corp",
      "external_ref": null,
      "monthly_allowance": 1000,
      "next_reset_at": "2026-08-01T00:00:00+00:00",
      "reset_due": false
    }
  ],
  "next_cursor": null
}
GET/v1/reseller/customers/:customerId

특정 고객의 예산 잔액과 상태를 조회합니다. 고객이 없거나 삭제된 경우 404.

Response

{
  "customer_id": "user_abc123",
  "cap": 2000,
  "spent": 450,
  "remaining": 1550,
  "status": "active",
  "external_ref": null,
  "monthly_allowance": 1000,
  "next_reset_at": "2026-08-01T00:00:00+00:00",
  "reset_due": false
}
PATCH/v1/reseller/customers/:customerId

고객의 표시 메타데이터(name / external_ref)를 부분 수정합니다. 생략한 필드는 유지, 명시적 null 또는 빈 문자열은 제거. 고객이 없으면 404.

Request Body

ParameterTypeRequiredDescription
namestring | nullNo표시 이름 (최대 200자). null 또는 빈 문자열이면 제거. 생략 시 유지.
external_refstring | nullNo외부 참조/메모 (최대 512자). null 또는 빈 문자열이면 제거. 생략 시 유지.

두 필드 모두 생략하면 400. cap은 이 엔드포인트로 수정할 수 없습니다 — 증액은 POST .../topup(멱등), 월간 예산은 PUT .../budget을 사용하세요.

Response

{
  "customer_id": "user_abc123",
  "cap": 2000,
  "spent": 450,
  "remaining": 1550,
  "status": "active",
  "name": "Acme Corp (renamed)",
  "external_ref": "crm-4412",
  "monthly_allowance": null,
  "next_reset_at": null,
  "reset_due": false
}
DELETE/v1/reseller/customers/:customerId

고객 지갑을 소프트 삭제합니다 (사용 이력은 유지). 성공 시 204 No Content.

응답 본문 없음 (204).

충전 · 예산

POST/v1/reseller/customers/:customerId/topup

고객 cap을 충전합니다. 멱등(idempotent) — 동일 idempotency_key 재요청 시 applied: false.

Request Body

ParameterTypeRequiredDescription
amountnumberYes충전할 크레딧 금액 (> 0)
idempotency_keystringYes멱등성 키 (UUID v4 권장). 허용 문자: [A-Za-z0-9_.:-], 최대 128자. 동일 키 재요청은 no-op.

Response

{
  "applied": true,
  "cap": 3000
}
POST/v1/reseller/customers/topup/bulk

최대 100명을 한 번에 충전합니다. 항목별 idempotency_key로 각각 멱등 — 부분 성공.

Request Body

ParameterTypeRequiredDescription
topupsobject[]Yes충전 항목 배열 (1~100개). 각 항목: { customer_id, amount, idempotency_key }

Response

{
  "results": [
    { "customer_id": "user_abc123", "applied": true },
    { "customer_id": "user_def456", "applied": false }
  ],
  "errors": []
}

applied: false는 이미 사용된 idempotency_key (안전한 재시도). errors에는 실제 실패만 담깁니다.

PUT/v1/reseller/customers/:customerId/budget

월간 반복 예산을 설정하거나 제거(monthly_allowance: null)합니다. 고객이 없으면 404.

Request Body

ParameterTypeRequiredDescription
monthly_allowancenumber | nullNo월간 예산 (> 0). null이면 반복 예산 제거. 설정 즉시 크레딧이 지급되지는 않음 — 즉시 충전은 topup 사용.

Response

{
  "customer_id": "user_abc123",
  "monthly_allowance": 1000,
  "next_reset_at": "2026-08-01T00:00:00+00:00"
}

정지 · 재활성화

POST/v1/reseller/customers/:customerId/suspend

고객을 정지합니다 — 이후 요청은 402 customer_suspended로 차단, 이력은 유지. 고객이 없으면 404.

Response

{
  "customer_id": "user_abc123",
  "status": "suspended"
}
POST/v1/reseller/customers/:customerId/reactivate

정지된 고객을 재활성화합니다. 고객이 없으면 404.

Response

{
  "customer_id": "user_abc123",
  "status": "active"
}
POST/v1/reseller/customers/:customerId/restore

소프트 삭제된 고객을 복구합니다 (cap·spent 이력 보존). 탈퇴 후 같은 auth user id로 재가입할 때 사용.

Response

{
  "customer_id": "user_abc123",
  "cap": 1000,
  "spent": 250,
  "remaining": 750,
  "status": "active"
}

DELETE는 소프트 삭제라 해당 customer_id는 계속 점유됩니다 — 재생성은 409, reactivate는 404. 복구는 spent와 usage 로그를 그대로 보존하므로 reconcile 불변식이 유지됩니다. 에러: 404 복구할 삭제 지갑 없음(존재한 적 없거나 이미 활성 — 정지된 고객은 reactivate 사용), 429 삭제 이후 팀이 고객 수 한도에 도달(한도 우회 불가), 503 일시적 충돌(재시도 안전).

Phantom 토큰 (엔드커스터머 파일 접근)

POST/v1/reseller/customers/:customerId/token

엔드커스터머용 단기 스코프 토큰(phantom token)을 발급합니다. 무료(크레딧 차감 없음). 고객이 없으면 404, suspended/deleted면 403.

Request Body

ParameterTypeRequiredDescription
ttl_secondsintegerNo토큰 수명 (초). 최소 300 (5분), 최대 3600 (60분), 기본 900 (15분). 범위를 벗어나면 400.
scopestring[]No허용 스코프의 부분집합: "files:list", "files:sign". 기본은 둘 다.

Response

{
  "token": "eyJ2IjoxLC4uLn0.c2ln",
  "expires_at": "2026-07-30T00:15:00+00:00",
  "scope": ["files:list", "files:sign"],
  "customer_id": "user_abc123"
}

발급된 토큰은 엔드커스터머가 X-Phantom-Token 헤더로 GET .../customers/:customerId/filesPOST /files/sign을 직접 호출할 때 사용합니다 — 리셀러의 실제 API 키는 절대 노출되지 않습니다. 지갑을 suspend/delete하면 해당 고객의 모든 토큰이 즉시 거부됩니다 (소프트 revocation).

POST/v1/reseller/customers/:customerId/token/revoke

발급한 phantom token을 TTL 만료 전에 개별 폐기합니다. 멱등 — 이미 폐기됐거나 만료된 토큰도 200.

Request Body

ParameterTypeRequiredDescription
tokenstringYes폐기할 phantom token 문자열 (발급 응답의 token 값).

Response

{
  "revoked": true
}

자기 워크스페이스의 자기 고객용 토큰만 폐기할 수 있습니다 — 다른 테넌트의 토큰은 403 token_not_owned. 실패는 절대 조용히 성공으로 위장하지 않습니다: jti가 없는 레거시 토큰은 409 token_not_revocable (지갑 suspend로 대체), 폐기 저장소 장애 시 503 revocation_unavailable (토큰은 아직 살아있음 — 재시도 필요).

POST/v1/reseller/customers/:customerId/token/revoke-all

해당 고객에게 발급된 모든 미만료 phantom token을 한 번에 무효화합니다. 토큰 원문 불필요. 고객이 없으면 404.

Response

{
  "revoked": true,
  "customer_id": "user_abc123",
  "token_epoch": 3
}

개별 폐기(/token/revoke)와 달리 토큰 원문이 필요 없습니다 — 지갑의 token_epoch를 원자적으로 +1 하여, 이전 epoch로 발급된 토큰 전부를 검증 단계에서 즉시 거부합니다 (epoch 기반 전체 무효화). “토큰이 유출됐는데 원문은 모른다”는 사고 대응용입니다. suspend와 달리 고객 서비스는 계속되며, 새 토큰을 바로 재발급하면 됩니다. 멱등은 아니지만 (호출마다 epoch +1) 부작용은 동일합니다 — 호출 시점 이전에 발급된 모든 토큰이 무효화됩니다. 응답의 token_epoch는 지갑의 새 epoch 값입니다.

사용량

GET/v1/reseller/customers/:customerId/usage

고객별 사용 레코드 목록을 조회합니다 (최신순).

Query Parameters

ParameterTypeRequiredDescription
start_datestring (ISO 8601)No조회 시작 일시
end_datestring (ISO 8601)No조회 종료 일시
limitintegerNo반환할 최대 레코드 수 (1~1000, 기본: 100)

Response

{
  "records": [
    {
      "job_id": "pred_abc123",
      "timestamp": "2026-06-30T11:00:00Z",
      "model": "black-forest-labs/flux-schnell",
      "credits_used": 10,
      "final_status": "completed",
      "customer_id": "user_abc123"
    }
  ]
}
GET/v1/reseller/customers/:customerId/usage/summary

고객 한 명의 집계: 총합 + 모델별 분해. group_by=day로 일별 분해 추가 (명세서 라인아이템용).

Query Parameters

ParameterTypeRequiredDescription
start_datestring (ISO 8601)No조회 시작 일시
end_datestring (ISO 8601)No조회 종료 일시
group_bystringNo"day"만 허용. 지정 시 by_day 배열 추가.

Response

{
  "customer_id": "user_abc123",
  "total_credits": 450.5,
  "total_requests": 128,
  "failed_requests": 3,
  "by_model": [
    { "model": "black-forest-labs/flux-schnell", "credits": 300.5, "count": 100 }
  ],
  "by_day": [
    { "date": "2026-06-30", "credits": 120.0, "count": 34, "failed": 1 }
  ]
}

total_requests는 성공 요청만 집계 (invoice-grade). 실패/취소는 failed_requests로 분리되며 크레딧을 차지하지 않습니다.

GET/v1/reseller/usage/summary

워크스페이스 전체의 고객별 사용량 롤업. format=csv로 정산용 CSV 다운로드.

Query Parameters

ParameterTypeRequiredDescription
start_datestring (ISO 8601)No조회 시작 일시
end_datestring (ISO 8601)No조회 종료 일시
group_bystringNo"day"만 허용. 워크스페이스 전체 일별 분해 추가 (JSON에서만).
formatstringNo"json" (기본) 또는 "csv"

Response (JSON)

{
  "customers": [
    { "customer_id": "user_abc123", "credits": 450.5, "count": 128, "failed": 3 }
  ],
  "total_credits": 1250.75,
  "total_requests": 342,
  "failed_requests": 8
}

Response (CSV)

customer_id,credits,requests,failed_requests
user_abc123,450.5,128,3

CSV는 customer-usage.csv 첨부파일로 반환됩니다 (Content-Disposition: attachment).

GET/v1/reseller/customers/:customerId/files

고객이 생성/업로드한 파일(예측 결과 + 업로드 입력)을 신선한 다운로드 URL과 함께 조회. team_id는 API 키에서, customer_id는 경로에서 강제되어 다른 고객 파일이 섞이지 않음. 고객이 없으면 404.

Query Parameters

ParameterTypeRequiredDescription
folderstringNo폴더 경로 필터
content_typestringNoMIME 타입 필터
sourcestringNo출처 필터 (예: "ai_output", "upload")
start_datestring (ISO 8601)No조회 시작 일시
end_datestring (ISO 8601)No조회 종료 일시
cursorstringNo페이지네이션 커서
limitintegerNo페이지당 최대 개수 (1~100, 기본: 50)
url_expirationintegerNo다운로드 URL 만료 시간(초). 최대 7일로 캡

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
}

콘솔(Clerk JWT)에서는 GET /teams/:teamId/customers/:customerId/files가 동일한 응답을 반환합니다.

Reconcile

GET/v1/reseller/customers/:customerId/reconcile

고객 한 명의 드리프트 셀프 체크: 지갑 미터(spent) vs 사용 로그. 고객이 없으면 404.

Response

{
  "customer_id": "user_abc123",
  "meter_spent": 450.5,
  "usage_credits": 450.5,
  "drift": 0.0,
  "in_sync": true
}
GET/v1/reseller/reconcile

워크스페이스 전체 드리프트 체크. 불일치 고객만 반환하므로 정상 상태면 응답이 작습니다.

Query Parameters

ParameterTypeRequiredDescription
limitintegerNo검사할 최대 지갑 수 (1~1000, 기본: 500)

Response

{
  "checked": 42,
  "in_sync": 41,
  "drifted": [
    {
      "customer_id": "user_def456",
      "meter_spent": 120.0,
      "usage_credits": 110.0,
      "drift": 10.0
    }
  ],
  "truncated": false
}

truncated: true는 limit 밖에 검사하지 않은 지갑이 남아 있다는 뜻. 허용 오차(0.01 크레딧) 이내의 차이는 in_sync로 간주합니다.

Error Responses

API 오류 시 다음 형식의 JSON 응답이 반환됩니다:

{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits. Required: 5, Available: 2",
    "details": {
      "required": 5,
      "available": 2
    }
  }
}
HTTP CodeError CodeDescription
400invalid_request잘못된 요청 형식
401unauthorizedAPI 키 없음 또는 유효하지 않음
402insufficient_credits크레딧 부족
404not_found리소스를 찾을 수 없음
429rate_limit_exceeded요청 횟수 또는 LLM 토큰 한도 초과
500internal_error서버 내부 오류