Skip to main content
Core.Today

File Upload & Storage

파일 업로드, 검색, 메타데이터 관리를 위한 Storage API입니다.

파일 업로드가 필요한 경우

  • Image-to-Image: 참조 이미지 기반 이미지 편집/변환
  • Image-to-Video: 첫 프레임 이미지로 비디오 생성
  • Voice Cloning: 음성 복제를 위한 샘플 오디오
  • ControlNet: 포즈/깊이 맵 등 제어 이미지
1

Presigned URL 발급

먼저 파일 업로드를 위한 presigned URL을 발급받습니다. 이 시점에 파일 메타데이터가 스토리지에 등록됩니다.

curl -X POST https://api.core.today/v1/files/upload-url \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"filename": "reference_image.png", "folder": "inputs"}'

메타데이터 저장

커스텀 메타데이터를 함께 저장할 수 있습니다 (최대 10KB):

curl -X POST https://api.core.today/v1/files/upload-url \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "data",
    "folder": "projects/my-project",
    "metadata": {
      "description": "프로젝트 데이터",
      "tags": ["dataset", "training"],
      "version": "1.0"
    }
  }'

응답 예시

{
  "upload_url": "https://s3.ap-northeast-2.amazonaws.com/files.core.today",
  "upload_fields": {
    "Content-Type": "image/png",
    "key": "aiapi/f04423dfab11/inputs/1765720407215_reference_image.png",
    "x-amz-algorithm": "AWS4-HMAC-SHA256",
    "x-amz-credential": "ASIA.../20251214/ap-northeast-2/s3/aws4_request",
    "x-amz-date": "20251214T120000Z",
    "x-amz-security-token": "IQoJb3JpZ2luX2VjEG0a...",
    "policy": "eyJleHBpcmF0aW9uIjoiMjAyNS0xMi0xNFQxMzowMDowMFoiLCJjb25kaXRpb25zIjpbey...",
    "x-amz-signature": "e6444e667072a27fd5589241803c8abf..."
  },
  "file_url": "https://files.core.today/aiapi/.../reference_image.png?Expires=...&Signature=...&Key-Pair-Id=...",
  "object_key": "aiapi/f04423dfab11/inputs/1765720407215_reference_image.png",
  "folder": "inputs"
}
2

S3에 파일 업로드

발급받은 upload_url upload_fields를 사용하여 S3에 직접 파일을 업로드합니다.

cURL

# upload_fields의 모든 필드를 순서대로 추가하고, file은 마지막에 추가
curl -X POST "https://s3.ap-northeast-2.amazonaws.com/files.core.today" \
  -F "Content-Type=image/png" \
  -F "key=aiapi/f04423dfab11/inputs/1765720407215_reference_image.png" \
  -F "x-amz-algorithm=AWS4-HMAC-SHA256" \
  -F "x-amz-credential=ASIA.../20251214/ap-northeast-2/s3/aws4_request" \
  -F "x-amz-date=20251214T120000Z" \
  -F "x-amz-security-token=IQoJb3JpZ2luX2VjEG0a..." \
  -F "policy=eyJleHBpcmF0aW9uIjoiMjAyNS0xMi0xNFQxMzowMDowMFoiLCJjb25kaXRpb25zIjpbey..." \
  -F "x-amz-signature=e6444e667072a27fd5589241803c8abf..." \
  -F "file=@/path/to/reference_image.png"

JavaScript

async function uploadFile(file, apiKey) {
  // 1. Get presigned URL
  const urlResponse = await fetch(
    "https://api.core.today/v1/files/upload-url",
    {
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ filename: file.name, folder: "inputs" })
    }
  );
  const { upload_url, upload_fields, file_url } = await urlResponse.json();

  // 2. Upload to S3
  const formData = new FormData();
  Object.entries(upload_fields).forEach(([key, value]) => {
    formData.append(key, value);
  });
  formData.append("file", file); // file must be last!

  await fetch(upload_url, {
    method: "POST",
    body: formData
  });

  // 3. Return the CDN URL (use this in prediction requests)
  return file_url;
}

Python

import requests

def upload_file(filepath, api_key, folder="inputs"):
    filename = filepath.split("/")[-1]

    # 1. Get presigned URL
    url_response = requests.post(
        "https://api.core.today/v1/files/upload-url",
        headers={
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        },
        json={"filename": filename, "folder": folder}
    )
    data = url_response.json()

    # 2. Upload to S3
    with open(filepath, "rb") as f:
        files = {"file": (filename, f)}
        requests.post(
            data["upload_url"],
            data=data["upload_fields"],
            files=files
        )

    # 3. Return the CDN URL (use this in prediction requests)
    return data["file_url"]

중요

  • file 필드는 반드시 마지막에 추가해야 합니다.
  • 모든 upload_fields 값을 그대로 전달해야 합니다.
3

모델 입력에 사용

업로드 완료 후 file_url을 모델의 입력 파라미터에 사용합니다.

Image-to-Image (FLUX Kontext)

curl -X POST https://api.core.today/v1/predictions \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-kontext-pro",
    "input": {
      "prompt": "change the background to a beach sunset",
      "image_url": "https://cdn.core.today/inputs/abc123/reference_image.png"
    }
  }'

Image-to-Video (Kling)

curl -X POST https://api.core.today/v1/predictions \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-1.6-pro",
    "input": {
      "prompt": "camera slowly zooms in, gentle wind blowing",
      "image": "https://cdn.core.today/inputs/abc123/first_frame.jpg",
      "duration": 5
    }
  }'

Voice Cloning (Fish Speech)

curl -X POST https://api.core.today/v1/predictions \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "fish-speech-1.5",
    "input": {
      "text": "이 문장은 업로드한 음성과 똑같은 목소리로 읽힙니다.",
      "reference_audio": "https://cdn.core.today/inputs/abc123/voice_sample.wav"
    }
  }'

파일 검색 API

업로드된 파일을 다양한 조건으로 검색할 수 있습니다.

POST /files/storage/search

요청 파라미터

파라미터타입설명
filtersobject검색 조건 (메타데이터 필드 기반)
sort_bystring정렬 기준 필드 (기본값: created_at)
sort_orderstring"asc" 또는 "desc" (기본값: desc)
pageinteger페이지 번호 (기본값: 1)
page_sizeinteger페이지당 결과 수 (기본값: 20, 최대: 100)

검색 예제

# 최근 업로드된 PNG 파일 검색
curl -X POST https://api.core.today/v1/files/storage/search \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "content_type": "image/png",
      "folder": "inputs"
    },
    "sort_by": "created_at",
    "sort_order": "desc",
    "page_size": 10
  }'

응답 예시

{
  "files": [
    {
      "object_key": "aiapi/team123/inputs/1734175200000_photo.png",
      "filename": "photo.png",
      "content_type": "image/png",
      "size": 1048576,
      "folder": "inputs",
      "created_at": "2025-12-14T10:00:00Z",
      "url": "https://files.core.today/...",
      "metadata": {
        "project": "avatar-gen",
        "tags": ["portrait", "input"]
      }
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 10,
  "total_pages": 5
}

검색 가능한 메타데이터 필드

40개의 인덱싱된 필드를 사용하여 파일을 검색할 수 있습니다.

Identity 필드

file_id - 파일 고유 ID
object_key - S3 객체 키
filename - 원본 파일명
team_id - 팀 ID
user_id - 사용자 ID

Time 필드

created_at - 생성 시간
updated_at - 수정 시간
expires_at - 만료 시간

Source 필드

source - 출처 (upload/prediction)
source_job_id - 원본 작업 ID
source_model - 생성 모델명
upload_ip - 업로드 IP

Object 필드

content_type - MIME 타입
size - 파일 크기 (bytes)
extension - 확장자
folder - 폴더 경로
checksum - MD5 해시

Classification 필드

category - 카테고리
subcategory - 서브카테고리
tags - 태그 배열
labels - 라벨 배열
project - 프로젝트명
environment - 환경 (dev/prod)

Status 필드

status - 상태 (pending/active/archived)
visibility - 공개 범위
is_processed - 처리 완료 여부
is_archived - 아카이브 여부

Metrics 필드

width - 이미지/비디오 너비
height - 이미지/비디오 높이
duration - 오디오/비디오 길이(초)
frame_count - 비디오 프레임 수
sample_rate - 오디오 샘플레이트
bit_depth - 오디오 비트심도

AI/ML Context 필드

prompt - 생성 프롬프트
negative_prompt - 네거티브 프롬프트
seed - 랜덤 시드
steps - 생성 스텝 수
cfg_scale - CFG 스케일
model_version - 모델 버전

Compliance 필드

retention_days - 보관 기간
legal_hold - 법적 보존 여부
classification_level - 보안 등급

고유값 조회 API

특정 필드의 고유값 목록을 조회합니다. 필터링 UI 구성에 유용합니다.

GET /files/storage/distinct/{field}
# 사용된 모든 폴더 목록 조회
curl -X GET "https://api.core.today/v1/files/storage/distinct/folder" \
  -H "X-API-Key: cdt_your_api_key"

# 응답 예시
{
  "field": "folder",
  "values": ["inputs", "outputs", "projects/avatars", "projects/backgrounds"],
  "count": 4
}

지원 필드

folder, content_type, extension, category, project, tags, source_model, status

스키마 필드 조회 API

검색 가능한 모든 필드와 타입 정보를 조회합니다.

GET /files/storage/schema/fields
curl -X GET "https://api.core.today/v1/files/storage/schema/fields" \
  -H "X-API-Key: cdt_your_api_key"

# 응답 예시
{
  "fields": [
    {"name": "file_id", "type": "keyword", "searchable": true},
    {"name": "filename", "type": "text", "searchable": true},
    {"name": "created_at", "type": "date", "searchable": true},
    {"name": "size", "type": "long", "searchable": true},
    {"name": "tags", "type": "keyword", "searchable": true, "array": true}
  ],
  "total_fields": 40
}

메타데이터 수정 API

파일의 메타데이터를 업데이트합니다. 기존 메타데이터와 병합됩니다.

PATCH /files/storage/file/metadata
curl -X PATCH https://api.core.today/v1/files/storage/file/metadata \
  -H "X-API-Key: cdt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "object_key": "aiapi/team123/inputs/1734175200000_photo.png",
    "metadata": {
      "tags": ["portrait", "approved"],
      "status": "reviewed",
      "reviewer": "user@example.com"
    }
  }'

# 응답 예시
{
  "success": true,
  "object_key": "aiapi/team123/inputs/1734175200000_photo.png",
  "updated_fields": ["tags", "status", "reviewer"],
  "updated_at": "2025-12-14T12:00:00Z"
}

주의사항

  • 시스템 필드(file_id, team_id, created_at 등)는 수정 불가
  • 메타데이터 전체 크기는 10KB를 초과할 수 없음

파일 삭제 API

파일과 메타데이터를 영구 삭제합니다.

DELETE /files/storage/file
curl -X DELETE "https://api.core.today/v1/files/storage/file?object_key=aiapi/team123/inputs/1734175200000_photo.png" \
  -H "X-API-Key: cdt_your_api_key"

# 응답 예시
{
  "success": true,
  "deleted_object_key": "aiapi/team123/inputs/1734175200000_photo.png",
  "deleted_at": "2025-12-14T12:00:00Z"
}

경고

  • 삭제된 파일은 복구할 수 없습니다
  • 자신의 팀에 속한 파일만 삭제할 수 있습니다

SDK 예제

Python - 파일 검색

import requests

API_KEY = "cdt_your_api_key"
BASE_URL = "https://api.core.today/v1"

def search_files(filters, page=1, page_size=20):
    """파일을 검색합니다."""
    response = requests.post(
        f"{BASE_URL}/files/storage/search",
        headers={
            "X-API-Key": API_KEY,
            "Content-Type": "application/json"
        },
        json={
            "filters": filters,
            "page": page,
            "page_size": page_size,
            "sort_by": "created_at",
            "sort_order": "desc"
        }
    )
    return response.json()

# 예: PNG 이미지 검색
results = search_files({
    "content_type": "image/png",
    "folder": "inputs"
})

for file in results["files"]:
    print(f"{file['filename']} - {file['size']} bytes")

# 예: 특정 프로젝트의 파일 검색
project_files = search_files({
    "project": "avatar-gen",
    "tags": ["approved"]
})

JavaScript - 파일 검색

const API_KEY = "cdt_your_api_key";
const BASE_URL = "https://api.core.today/v1";

async function searchFiles(filters, options = {}) {
  const { page = 1, pageSize = 20, sortBy = "created_at", sortOrder = "desc" } = options;

  const response = await fetch(`${BASE_URL}/files/storage/search`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      filters,
      page,
      page_size: pageSize,
      sort_by: sortBy,
      sort_order: sortOrder
    })
  });

  return response.json();
}

// 예: 최근 업로드된 이미지 검색
const images = await searchFiles({
  content_type: "image/png",
  folder: "inputs"
});

console.log(`Found ${images.total} files`);
images.files.forEach(file => {
  console.log(`${file.filename} - ${file.size} bytes`);
});

// 예: 특정 모델로 생성된 파일 검색
const generatedFiles = await searchFiles({
  source_model: "flux-schnell",
  created_at: { gte: "2025-12-01T00:00:00Z" }
});

메타데이터 설계 가이드

프로젝트별 관리

{
  "project": "avatar-generator",
  "category": "portraits",
  "environment": "production"
}

워크플로우 추적

{
  "status": "pending_review",
  "tags": ["needs-approval", "high-priority"],
  "assigned_to": "reviewer@company.com"
}

AI 생성 컨텍스트

{
  "source_model": "flux-schnell",
  "prompt": "A professional headshot",
  "seed": 12345,
  "steps": 28,
  "cfg_scale": 7.5
}

베스트 프랙티스

  • 일관된 네이밍: 모든 프로젝트에서 동일한 필드명 사용
  • 태그 활용: 여러 분류가 필요한 경우 tags 배열 활용
  • 환경 구분: dev/staging/production 환경별 파일 구분
  • 버전 관리: version 필드로 파일 버전 추적

파일 업로드 제한

파일 형식

모든 파일 형식 지원
이미지, 비디오, 오디오, 문서, 바이너리 등

최대 크기

50MB

파일 유효 기간

항목유효 기간설명
Presigned Upload URL7일URL 발급 후 7일 내 업로드 필요
업로드된 파일7일업로드 후 7일 뒤 자동 삭제
결과 파일 URL7일모델 실행 결과 URL 유효 기간

중요

  • 파일은 업로드 후 7일 뒤 자동으로 삭제됩니다
  • 장기 보관이 필요한 파일은 결과를 별도로 저장하세요
  • 스토리지 사용량은 대시보드 설정에서 확인할 수 있습니다

전체 예제 (Python)

import requests
import time

API_KEY = "cdt_your_api_key"
BASE_URL = "https://api.core.today/v1"

def upload_and_transform(image_path, prompt, project="default"):
    """이미지를 업로드하고 변환합니다."""

    # 1. Get presigned URL with metadata
    filename = image_path.split("/")[-1]
    url_response = requests.post(
        f"{BASE_URL}/files/upload-url",
        headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
        json={
            "filename": filename,
            "folder": f"projects/{project}/inputs",
            "metadata": {
                "project": project,
                "source": "upload",
                "original_filename": filename
            }
        }
    )
    url_data = url_response.json()

    # 2. Upload to S3
    with open(image_path, "rb") as f:
        requests.post(
            url_data["upload_url"],
            data=url_data["upload_fields"],
            files={"file": (filename, f)}
        )

    # 3. Create prediction with uploaded image
    pred_response = requests.post(
        f"{BASE_URL}/predictions",
        headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
        json={
            "model": "flux-kontext-pro",
            "input": {
                "prompt": prompt,
                "image_url": url_data["file_url"]
            }
        }
    )
    job_id = pred_response.json()["job_id"]

    # 4. Poll for result
    while True:
        result = requests.get(
            f"{BASE_URL}/predictions/{job_id}",
            headers={"X-API-Key": API_KEY}
        ).json()

        if result["status"] == "completed":
            return result["result"]
        elif result["status"] == "failed":
            raise Exception(result.get("error"))

        time.sleep(2)

# Usage
result = upload_and_transform(
    "/path/to/photo.jpg",
    "make it look like a watercolor painting",
    project="art-styles"
)
print(f"Result: {result}")