Skip to main content
Core.Today

File Upload & Storage

The Storage API for file upload, search, and metadata management.

When you need file upload

  • Image-to-Image: Reference-image-based image editing/transformation
  • Image-to-Video: Generate video from a first-frame image
  • Voice Cloning: Sample audio for voice cloning
  • ControlNet: Control images such as pose/depth maps
Recommended

Simple Upload (Multipart)

POST /predictions/upload lets you handle both the file upload and the prediction request in a single API call. There is no need to separately issue a presigned URL or upload directly to S3.

File Field Rules

file:<input_key> as the file field name, and the uploaded file's URL is automatically injected into input[input_key].

File Field NameResult
file:imageinput["image"] = "https://..."
file:image (x2)input["image"] = ["url1", "url2"]

cURL

# Single file - Image-to-Image
curl -X POST https://api.core.today/v1/predictions/upload \
  -H "X-API-Key: cdt_your_api_key" \
  -F "model=flux-kontext-pro" \
  -F 'input={"prompt":"change background to a beach sunset"}' \
  -F "file:image_url=@photo.png"

# Multiple files - send several files under the same key
curl -X POST https://api.core.today/v1/predictions/upload \
  -H "X-API-Key: cdt_your_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"

Python

import requests, json

with open("photo.png", "rb") as f:
    response = requests.post(
        "https://api.core.today/v1/predictions/upload",
        headers={"X-API-Key": "cdt_your_api_key"},
        data={
            "model": "black-forest-labs/flux-kontext-pro",
            "input": json.dumps({"prompt": "change background to a beach sunset"}),
        },
        files={"file:image_url": ("photo.png", f, "image/png")},
    )

job = response.json()
print(job["job_id"])  # then poll GET /predictions/{job_id} for the result

JavaScript

const formData = new FormData();
formData.append("model", "black-forest-labs/flux-kontext-pro");
formData.append("input", JSON.stringify({ prompt: "change background to a beach sunset" }));
formData.append("file:image_url", fileInput.files[0]);

const response = await fetch("https://api.core.today/v1/predictions/upload", {
  method: "POST",
  headers: { "X-API-Key": "cdt_your_api_key" },
  body: formData,
});

const job = await response.json();
console.log(job.job_id);

Form Fields

FieldTypeRequiredDescription
modelstringYesModel ID
inputstring (JSON)NoModel input JSON string (default: "{}")
is_publicstringNo"true" or "false" (default: "false")
output_folderstringNoOutput storage folder path
webhook_urlstringNoWebhook URL to call on completion
file:<input_key>fileNoFile to upload (max 50MB per file, multiple files allowed under the same key)

Note

  • The maximum size per file is 50MB.
  • The response format is identical to POST /predictions. After that, poll for the result via GET /predictions/{job_id}.
  • Uploaded files are stored in S3 storage, and a CDN URL accessible for 7 days is generated.

Presigned URL Flow (Advanced)

For large files or when you need to show upload progress, you can use the 3-step flow below. It is also useful when you want to upload a file in advance and reuse it across multiple predictions.

1

Issue a Presigned URL

First, issue a presigned URL for the file upload. At this point the file metadata is registered in storage.

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"}'

Storing Metadata

You can store custom metadata alongside the file (up to 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": "Project data",
      "tags": ["dataset", "training"],
      "version": "1.0"
    }
  }

Example Response

{
  "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"
}

Storage Quota Exceeded (403)

When a team has exceeded its per-plan storage quota (table above), calling POST /files/upload-url returns 403 along with a storage_quota_exceeded code. This is separate from the 50MB per-file limit, and no matter how small a single file is, a new upload is rejected once the team's total usage exceeds the limit. AI prediction outputs are not blocked by this quota.

{
  "detail": {
    "message": "Storage limit exceeded. Limit: 5.0 GB, Used: 5.0 GB, Requested: 0 B",
    "code": "storage_quota_exceeded"
  }
}

message is a human-readable description string whose wording varies depending on whether the byte or file-count limit was exceeded (subject to change) — always branch on the code value (storage_quota_exceeded). To resolve: free up space by deleting old files (check current usage with GET /files/storage), or upgrade to a higher plan.

2

Upload the File to S3

Using the issued upload_url and upload_fields, upload the file directly to S3.

cURL

# Add all upload_fields in order, and add file last
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"]

Important

  • file field must always be added last.
  • Pass all upload_fields values through exactly as provided.
3

Use in Model Input

After the upload completes, use file_url as the model's input parameter.

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": "black-forest-labs/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": "kwaivgi/kling-v2.1",
    "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": "minimax/speech-02-turbo",
    "input": {
      "text": "This sentence is read in the exact same voice as the audio you uploaded.",
      "reference_audio": "https://cdn.core.today/inputs/abc123/voice_sample.wav"
    }
  }

Storage API Endpoints

Beyond file upload, a variety of APIs are provided for storage management, search, and metadata management.

MethodEndpointDescription
Upload
POST/files/upload-urlIssue a presigned URL
Storage Management
GET/files/storageUsage query (size, file count, breakdown by type)
GET/files/storage/filesList files (pagination, folder filter)
GET/files/storage/file/{object_key}Get a single file (metadata + download URL)
GET/files/storage/foldersList folders (file count, size)
POST/files/storage/folderCreate a folder (pre-create an empty folder)
File Management
PATCH/files/storage/file/metadataUpdate metadata
POST/files/storage/file/publicSet public/private
DELETE/files/storage/fileDelete a file
Search
POST/files/storage/searchMetadata-based file search
GET/files/storage/distinct/{field}Get distinct values by field
GET/files/storage/schema/fieldsList of searchable fields

Storage Usage Query

Query the current team's storage usage.

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

# Example response
{
  "total_bytes": 52428800,
  "total_mb": 50.0,
  "file_count": 127,
  "formatted_size": "50.0 MB",
  "by_content_type": [
    {"content_type": "image/png", "size": 31457280, "count": 85},
    {"content_type": "video/mp4", "size": 15728640, "count": 12},
    {"content_type": "audio/wav", "size": 5242880, "count": 30}
  ]
}

Response Fields

FieldTypeDescription
total_bytesintegerTotal usage (bytes)
total_mbfloatTotal usage (MB)
file_countintegerTotal file count
formatted_sizestringFormatted usage (e.g., "50.0 MB")
by_content_typearrayUsage breakdown by content type

List Files

List uploaded files with pagination.

GET /files/storage/files

Query Parameters

ParameterTypeDescription
folderstringFilter by folder path (optional)
limitintegerNumber of files to return (default: 50)
offsetintegerNumber of files to skip (default: 0)
url_expirationintegerDownload URL expiration (seconds). 300–15,552,000 (5 min–180 days). Default: 604,800 (7 days)
# Default query (7-day URL)
curl -X GET "https://api.core.today/v1/files/storage/files?folder=inputs&limit=10" \
  -H "X-API-Key: cdt_your_api_key"

# Query with a 1-hour expiration URL
curl -X GET "https://api.core.today/v1/files/storage/files?folder=inputs&limit=10&url_expiration=3600" \
  -H "X-API-Key: cdt_your_api_key"

# Example response
{
  "files": [
    {
      "filename": "photo.png",
      "file_size": 1048576,
      "content_type": "image/png",
      "uploaded_at": "2025-12-14T10:00:00Z",
      "object_key": "aiapi/team123/inputs/1734175200000_photo.png",
      "folder": "inputs",
      "expires_at": "2025-12-21T10:00:00Z",
      "file_url": "https://files.core.today/..."
    }
  ],
  "total_count": 42
}

Get a Single File

Query a single file's metadata and download URL using its object key.

GET /files/storage/file/{object_key}

object_key is included directly in the URL path, the same way as the S3 REST API.

Query Parameters

ParameterTypeDescription
url_expirationintegerDownload URL expiration (seconds). 300–15,552,000 (5 min–180 days). Default: 604,800 (7 days)
# Default query (7-day URL)
curl -X GET "https://api.core.today/v1/files/storage/file/aiapi/team123/inputs/1734175200000_photo.png" \
  -H "X-API-Key: cdt_your_api_key"

# Query with a 5-minute expiration URL
curl -X GET "https://api.core.today/v1/files/storage/file/aiapi/team123/inputs/1734175200000_photo.png?url_expiration=300" \
  -H "X-API-Key: cdt_your_api_key"

# Example response
{
  "filename": "photo.png",
  "file_size": 1048576,
  "content_type": "image/png",
  "uploaded_at": "2025-12-14T10:00:00Z",
  "object_key": "aiapi/team123/inputs/1734175200000_photo.png",
  "folder": "inputs",
  "status": "uploaded",
  "expires_at": "2025-12-21T10:00:00Z",
  "file_url": "https://files.core.today/...",
  "metadata": {"project": "avatar-gen", "tags": ["portrait"]},
  "is_public": false,
  "public_url": null
}

List Folders

Query the storage folder structure. Includes each folder's file count and total size.

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

# Example response
{
  "folders": [
    {"name": "inputs", "path": "inputs", "file_count": 85, "total_size": 31457280},
    {"name": "outputs", "path": "outputs", "file_count": 30, "total_size": 15728640},
    {"name": "projects", "path": "projects", "file_count": 12, "total_size": 5242880}
  ]
}

Create a Folder

You can pre-create an empty folder before uploading files. Creating a folder that already exists returns the existing folder.

POST /files/storage/folder
docs.fileUpload.createFolder.code
ParameterTypeDescription
folder_namestring (required)Folder name (1–255 chars, special characters restricted)
parent_folderstring (optional)Parent folder path (root if empty)

File Search API

You can search uploaded files by various conditions.

POST /files/storage/search

Request Parameters

ParameterTypeDescription
filtersobjectSearch conditions (based on metadata fields)
sort_bystringSort field (default: created_at)
sort_orderstring"asc" or "desc" (default: desc)
pageintegerPage number (default: 1)
page_sizeintegerResults per page (default: 20, max: 100)
url_expirationintegerDownload URL expiration (seconds). 300–15,552,000 (5 min–180 days). Default: 604,800 (7 days)

Search Example

# Search recently uploaded PNG files
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
  }

Example Response

{
  "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
}

Searchable Metadata Fields

You can search files using 40 indexed fields.

Identity Fields

file_id - Unique file ID
object_key - S3 object key
filename - Original filename
team_id - Team ID
user_id - User ID

Time Fields

created_at - Creation time
updated_at - Update time
expires_at - Expiration time

Source Fields

source - Source (upload/prediction)
source_job_id - Source job ID
source_model - Generating model name
upload_ip - Upload IP

Object Fields

content_type - MIME type
size - File size (bytes)
extension - Extension
folder - Folder path
checksum - MD5 hash

Classification Fields

category - Category
subcategory - Subcategory
tags - Tag array
labels - Label array
project - Project name
environment - Environment (dev/prod)

Status Fields

status - Status (pending/active/archived)
visibility - Visibility scope
is_processed - Whether processing is complete
is_archived - Whether archived

Metrics Fields

width - Image/video width
height - Image/video height
duration - Audio/video duration (seconds)
frame_count - Video frame count
sample_rate - Audio sample rate
bit_depth - Audio bit depth

AI/ML Context Fields

prompt - Generation prompt
negative_prompt - Negative prompt
seed - Random seed
steps - Number of generation steps
cfg_scale - CFG scale
model_version - Model version

Compliance Fields

retention_days - Retention period
legal_hold - Whether under legal hold
classification_level - Security classification level

Distinct Values API

Query the list of distinct values for a specific field. Useful for building a filtering UI.

GET /files/storage/distinct/{field}
# List all folders in use
curl -X GET "https://api.core.today/v1/files/storage/distinct/folder" \
  -H "X-API-Key: cdt_your_api_key"

# Example response
{
  "field": "folder",
  "values": ["inputs", "outputs", "projects/avatars", "projects/backgrounds"],
  "count": 4
}

Supported Fields

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

Schema Fields API

Query all searchable fields and their type information.

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"

# Example response
{
  "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
}

Metadata Update API

Update a file's metadata. It is merged with the existing metadata.

PATCH /files/storage/file/metadata
docs.fileUpload.metaMod.code

Caution

  • System fields (file_id, team_id, created_at, etc.) cannot be modified
  • Total metadata size cannot exceed 10KB

File Deletion API

Permanently deletes the file and its metadata.

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"

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

Warning

  • Deleted files cannot be recovered
  • You can only delete files belonging to your own team

SDK Examples

Python — File Search

import requests

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

def search_files(filters, page=1, page_size=20):
    """Search files."""
    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()

# Example: search PNG images
results = search_files({
    "content_type": "image/png",
    "folder": "inputs"
})

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

# Example: search files in a specific project
project_files = search_files({
    "project": "avatar-gen",
    "tags": ["approved"]
})

JavaScript — File Search

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();
}

// Example: search recently uploaded images
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`);
});

// Example: search files generated by a specific model
const generatedFiles = await searchFiles({
  source_model: "black-forest-labs/flux-schnell",
  created_at: { gte: "2025-12-01T00:00:00Z" }
});

Metadata Design Guide

Per-project Management

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

Workflow Tracking

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

AI Generation Context

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

Best Practices

  • Consistent naming: Use the same field names across all projects
  • Use tags: Use the tags array when you need multiple classifications
  • Separate environments: Separate files by dev/staging/production environment
  • Versioning: Track file versions with the version field

File Upload Limits

File Formats

All file formats supported
Images, video, audio, documents, binaries, and more

Maximum Size

50MB

Storage Limits by Plan

File upload and storage usage do not consume credits. However, each plan has limits on total capacity and file count.

PlanFile StorageMax FilesDB Storage
Free1 GB1,000100 MB
Pro10 GB50,0005 GB
Team100 GB500,00050 GB
Enterprise1 TB10,000,000500 GB

You can check current usage on the dashboard's Storage page or via the GET /files/storage API.

File Validity Period

ItemValidity PeriodDescription
Presigned Upload URL7 daysUpload must happen within 7 days of URL issuance
Uploaded fileBy plan (see table below)Retention is independent of the URL validity period — even if the URL expires the file is not deleted, and it can be re-signed at any time within the retention period
Download URL (default)7 daysDefault expiration for file/result download URLs
Download URL (custom, storage query)5 min – 180 daysSet via the GET /files/storage/* storage query endpoint's url_expiration parameter (in seconds, 300–15,552,000)
Re-signed URL (/files/sign)Up to 7 daysPOST /files/sign's expiration is capped at 604,800 seconds (7 days)

Why the two caps differ: /files/sign re-signing is currently capped at up to 7 days, while generating a download URL directly from the storage query endpoints allows up to 180 days. This is not a bug but a per-endpoint policy difference, and either way the file remains even after the URL expires, so if you store the object_key you can reissue a URL at any time.

PlanFile Retention Period
Free30 days
Pro365 days
Team / EnterpriseUnlimited

Important

  • Uploaded files are kept for the per-plan retention period (table above), and until then they will not disappear unless you delete them yourself
  • Even if the download URL expires the file remains — if you store the object_key you can reissue a new URL via POST /files/sign
  • You can check storage usage in the dashboard settings

Re-sign a File (Refresh URL)

Download URLs expire after the default 7 days, but the file itself remains for the plan's retention period. POST /files/sign lets you reissue a fresh URL for any object this team owns at any time. Store the object_key (or job_id) instead of the URL — we recommend this, since the URL expires but the object_key stays valid as long as the file exists.

POST /files/sign
FieldTypeDescription
object_keystringThe S3 object key to re-sign. url/job_id — specify exactly one of the three.
urlstringA previously issued CloudFront URL (for re-signing)
job_idstringPrediction job ID — re-signs all result files owned by that job at once
expirationinteger (optional)Request expiration (seconds). Capped at up to 7 days (604,800 seconds)
docs.fileUpload.resign.code

A URL is issued only after ownership is verified — an object owned by another team or a nonexistent key both return 404 file_not_found the same way (so file existence is not leaked). In a reseller workspace, calling with the X-Customer-Id header limits re-signing to files attributed to that customer.

Re-referencing Previous Output (Model Chaining)

You can pass a previous prediction's output file without re-downloading or re-uploading it directly as the next prediction's input value. If the input contains a https://files.core.today/... URL that this team owns, the gateway automatically re-signs it with a fresh URL before forwarding to the provider. Even if the URL has already expired, it still works as long as you own the file. For example, you can feed an image generation result straight into a video model's input image.

  • Files not owned by this team, or URLs that are not on files.core.today, pass through unchanged (not an error) — a still-valid presigned URL or an external URL can be used as-is.
  • A single input can auto-re-sign up to 20 owned references.

Pattern 1 — Pass a stored URL as-is (auto re-sign)

docs.fileUpload.chaining.pattern1Code

Pattern 2 — Store the object_key and pass a new URL from /files/sign

docs.fileUpload.chaining.pattern2Code

Pattern 1 has the gateway re-sign automatically, so no separate /files/sign call is needed. Pattern 2 is useful when you need to validate/log the URL yourself or hand it to a client in advance. In both cases the file is never re-uploaded.

Complete Example (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"):
    """Upload an image and transform it."""

    # 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": "black-forest-labs/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}")