Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DecartAI/sdk/llms.txt
Use this file to discover all available pages before exploring further.
This example demonstrates how to build a complete REST API server using Express and the Decart SDK. It includes endpoints for both synchronous image generation and asynchronous video generation.
What You’ll Build
An Express server with:
- Text-to-image generation (synchronous)
- Image-to-image transformation (synchronous)
- Video generation with job management (asynchronous)
- Status checking and result retrieval
- Automatic polling endpoints
Prerequisites
- Node.js 18 or higher
- A Decart API key
Setup
Clone and navigate to the example
git clone https://github.com/decartai/sdk
cd sdk/examples/express-api
Configure your API key
Create a .env file:DECART_API_KEY=your-api-key-here
PORT=3000
Install dependencies
From the repository root:cd ../..
pnpm install
pnpm build
Complete Server Code
import "dotenv/config";
import express from "express";
import { createDecartClient, models } from "@decartai/sdk";
const app = express();
app.use(express.json());
const client = createDecartClient({
apiKey: process.env.DECART_API_KEY!,
});
// Generate image from text (sync - returns immediately)
app.post("/api/image/generate", async (req, res) => {
try {
const { prompt } = req.body;
const blob = await client.process({
model: models.image("lucy-pro-t2i"),
prompt,
});
const buffer = Buffer.from(await blob.arrayBuffer());
res.setHeader("Content-Type", "image/png");
res.send(buffer);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Transform image (sync - returns immediately)
app.post("/api/image/transform", async (req, res) => {
try {
const { prompt, imageUrl } = req.body;
const blob = await client.process({
model: models.image("lucy-pro-i2i"),
prompt,
data: imageUrl,
});
const buffer = Buffer.from(await blob.arrayBuffer());
res.setHeader("Content-Type", "image/png");
res.send(buffer);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Submit video generation job (async - returns job ID)
app.post("/api/video/generate", async (req, res) => {
try {
const { prompt } = req.body;
const job = await client.queue.submit({
model: models.video("lucy-pro-t2v"),
prompt,
});
res.json({ jobId: job.job_id, status: job.status });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Check video job status
app.get("/api/video/status/:jobId", async (req, res) => {
try {
const status = await client.queue.status(req.params.jobId);
res.json(status);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Get video result (when completed)
app.get("/api/video/result/:jobId", async (req, res) => {
try {
const blob = await client.queue.result(req.params.jobId);
const buffer = Buffer.from(await blob.arrayBuffer());
res.setHeader("Content-Type", "video/mp4");
res.send(buffer);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Generate video with automatic polling (convenience endpoint)
app.post("/api/video/generate-sync", async (req, res) => {
try {
const { prompt, videoUrl } = req.body;
const result = await client.queue.submitAndPoll({
model: videoUrl ? models.video("lucy-pro-v2v") : models.video("lucy-pro-t2v"),
prompt,
...(videoUrl && { data: videoUrl }),
});
if (result.status === "completed") {
const buffer = Buffer.from(await result.data.arrayBuffer());
res.setHeader("Content-Type", "video/mp4");
res.send(buffer);
} else {
res.status(500).json({ error: result.error });
}
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
console.log("");
console.log("Available endpoints:");
console.log(" POST /api/image/generate - Generate image from text");
console.log(" POST /api/image/transform - Transform image");
console.log(" POST /api/video/generate - Submit video job");
console.log(" GET /api/video/status/:id - Check job status");
console.log(" GET /api/video/result/:id - Get video result");
console.log(" POST /api/video/generate-sync - Generate video (wait for result)");
});
API Endpoints
Text-to-Image Generation
Generate an image from a text prompt (synchronous):
curl -X POST http://localhost:3000/api/image/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "A beautiful sunset over mountains"}' \
--output image.png
Transform an existing image with a prompt:
curl -X POST http://localhost:3000/api/image/transform \
-H "Content-Type: application/json" \
-d '{
"prompt": "Oil painting style",
"imageUrl": "https://example.com/image.jpg"
}' \
--output transformed.png
Video Generation (Async)
Submit a video generation job:
curl -X POST http://localhost:3000/api/video/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "A cat playing piano"}'
Response:
{
"jobId": "abc123",
"status": "pending"
}
Check job status:
curl http://localhost:3000/api/video/status/abc123
Response:
{
"job_id": "abc123",
"status": "processing"
}
Get completed video:
curl http://localhost:3000/api/video/result/abc123 --output video.mp4
Video Generation (Sync)
Generate video and wait for completion:
curl -X POST http://localhost:3000/api/video/generate-sync \
-H "Content-Type: application/json" \
-d '{"prompt": "A timelapse of clouds moving"}' \
--output video.mp4
Key Concepts
Process API (Synchronous)
The Process API returns results immediately:
const blob = await client.process({
model: models.image("lucy-pro-t2i"),
prompt: "A serene landscape",
});
// Convert to buffer and send
const buffer = Buffer.from(await blob.arrayBuffer());
res.setHeader("Content-Type", "image/png");
res.send(buffer);
Queue API (Asynchronous)
The Queue API uses job IDs for long-running tasks:
// Submit job
const job = await client.queue.submit({
model: models.video("lucy-pro-t2v"),
prompt: "A video prompt",
});
// Check status
const status = await client.queue.status(job.job_id);
// Get result when completed
if (status.status === "completed") {
const blob = await client.queue.result(job.job_id);
}
Set appropriate content types:
// For images
res.setHeader("Content-Type", "image/png");
// For videos
res.setHeader("Content-Type", "video/mp4");
Error Handling
Handle errors gracefully:
try {
const blob = await client.process({ /* ... */ });
// Success
} catch (error) {
res.status(500).json({ error: String(error) });
}
Testing with cURL
Here are complete test commands:
# Test text-to-image
curl -X POST http://localhost:3000/api/image/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "A sunset over mountains"}' \
--output test-image.png
# Test video generation (async)
curl -X POST http://localhost:3000/api/video/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "A cat playing piano"}'
# Test video generation (sync - may take a while)
curl -X POST http://localhost:3000/api/video/generate-sync \
-H "Content-Type: application/json" \
-d '{"prompt": "Waves crashing on a beach"}' \
--output test-video.mp4
Frontend Integration
Example React component using the API:
import { useState } from "react";
export function ImageGenerator() {
const [prompt, setPrompt] = useState("");
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleGenerate = async () => {
setLoading(true);
try {
const response = await fetch("http://localhost:3000/api/image/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const blob = await response.blob();
const url = URL.createObjectURL(blob);
setImageUrl(url);
} catch (error) {
console.error("Failed to generate image:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter a prompt"
/>
<button onClick={handleGenerate} disabled={loading}>
{loading ? "Generating..." : "Generate"}
</button>
{imageUrl && <img src={imageUrl} alt="Generated" />}
</div>
);
}
Production Considerations
- Add CORS - Configure CORS for frontend access
- Rate Limiting - Implement rate limiting to prevent abuse
- Authentication - Add API key or JWT authentication
- Job Storage - Store job IDs in a database for persistence
- Webhooks - Notify clients when jobs complete
- Caching - Cache results for common prompts
- Monitoring - Log requests and errors