Getting started
From an empty account to your first uploaded file, in five steps.
npm install @uploadcenter/sdk-jsimport { createClient } from "@uploadcenter/sdk-js";
const client = createClient({
baseUrl: "https://api.uploadscenter.com",
token: process.env.UPLOADCENTER_API_KEY, // an API key from your project settings
});Every method is generated from UploadCenter's OpenAPI schema, so autocomplete covers the full API surface — view the JS package on npm or the Python package on PyPI.
curl -X POST https://api.uploadscenter.com/v1/uploads/presign \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"project_id": "project_...",
"filename": "photo.jpg",
"size_bytes": 128000,
"mime_type": "image/jpeg",
"visibility": "private"
}'curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpgcurl -X POST https://api.uploadscenter.com/v1/uploads/complete \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"file_id": "file_..."}'Reading a file back — GET /v1/files/{file_id}/url — always requires a valid API key or session for private files. Public files work differently — see below.
GET /v1/files/{file_id}
{
"id": "file_...",
"visibility": "public",
"status": "ready",
"url": "https://cdn.uploadscenter.com/o/…/photo.jpg"
}Private files (the default) don't get a url field — call GET /v1/files/{id}/url instead, which returns a fresh signed link valid for a limited time.
POST <your webhook URL>
Content-Type: application/json
X-Signature: sha256=<hex-encoded HMAC-SHA256>
X-Timestamp: <unix timestamp, seconds>
{"event":"file.processed","created_at":"2026-01-01T12:00:00Z","data":{"id":"file_...","project_id":"project_...","status":"ready", ...}}Events: file.uploaded, file.processed, file.failed, file.quarantined, file.flagged, storage.limit_reached. Up to 3 delivery attempts (30s, 2min, 10min backoff) before a delivery is marked dead.
import crypto from "node:crypto";
// Sign over "${timestamp}.${rawBody}" — rawBody must be the exact bytes
// received, not a re-serialized JSON.stringify(JSON.parse(rawBody)).
function verifyWebhook(rawBody: string, timestamp: string, signatureHeader: string, secret: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const provided = signatureHeader.replace(/^sha256=/, "");
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"));
}
// signatureHeader = req.headers["x-signature"]
// timestamp = req.headers["x-timestamp"]GET /v1/files/{file_id}/transform?w=480&h=320&format=auto&fit=cover
# fit: "cover" (crop to fill) or "inside" (contain)
# format: auto (default) | webp | jpeg | png
# quality: auto (default) | 1-100
#
# format=auto picks WebP or JPEG based on the request's Accept header —
# no more branching client-side for older browsers. quality=auto applies
# a sensible per-format default (80 for WebP, 82 for JPEG). Pass explicit
# values any time you want full control instead.PUT /v1/projects/{project_id}/watermark
{"file_id": "file_..."} # any image already uploaded to the project
GET /v1/files/{file_id}/transform?w=1200&watermark=true&gravity=south_east&opacity=60&overlay_scale=0.2
# gravity: north_west | north_east | south_west | south_east | center
# opacity: 1-100 (default 60)
# overlay_scale: 0.05-1.0 (default 0.2, fraction of the output image's width)POST /v1/domains
{"project_id": "project_...", "hostname": "cdn.yourapp.com"}
# Add the returned TXT record at your DNS provider, then:
POST /v1/domains/{domain_id}/verify
# Once verified, point cdn.yourapp.com at UploadCenter (A/CNAME — shown in
# the dashboard) and a TLS certificate is issued automatically. From then
# on, every public file's url uses your domain instead of the shared CDN.GET /v1/files/{file_id}
{
"id": "file_...",
"status": "ready",
"moderation_status": "flagged", // null (not evaluated) | "clean" | "flagged" | "error"
"moderation_score": 0.87
}A flagged file stays exactly as uploaded — your app decides what "flagged" should mean for your product. Available on paid plans; see the features page.
GET /v1/files/{file_id}/variants
[
{ "variant": "thumbnail", "format": "jpg", "width": 640, "height": 360, "url": "https://…" },
{ "variant": "720p", "format": "mp4", "width": 1280, "height": 720, "url": "https://…" }
]const generation = await client.ai.generateImageEndpointV1AiImagesGeneratePost({
project_id: projectId,
prompt: "a minimalist logo of a fox, flat vector style",
use_brand_kit: true, // pulls in this org's brand kit colors/fonts
});
// 202 Accepted — poll until it settles (or listen for the webhook below).
const result = await client.ai.getGenerationEndpointV1AiGenerationsGenerationIdGet(
generation.id,
projectId,
);
// result.file_ids — the new file(s), same shape as anything you uploadThe result is an ordinary file — file_ids on the finished generation — with its own visibility, delivery, and lifecycle, exactly like anything you upload directly.
GET /v1/ai/search?project_id={project_id}&q=cat+sitting+on+a+couch&limit=20
# Ranks files by AI caption similarity (files.ai_caption, produced by the
# analyze-image capability) — not full-text search over filenames.
# GET /v1/files/{file_id}/similar finds files with a similar caption.Want the full picture?
See every capability — versioning, custom domains, team roles, and more — on the features page.