Moodboards
Discover and apply moodboard style templates to image and video generations.
Moodboards are style templates that guide the visual direction of your generations. Each moodboard has a master_prompt — a curated description of colors, lighting, mood, and aesthetic — that is blended with your prompt at generation time.
You can apply a moodboard to image generation, image editing, and Creative Director requests using the moodboard_id field.
List Moodboards
Returns all moodboards available to your account. By default this includes your personal moodboards, system presets, and organization moodboards. Use the scope param to filter.
Endpoint
GET /api/v1/moodboardsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
scope | string | Filter by ownership, matched case-insensitively after trimming: personal (your own), preset or global (system presets), organization (org-shared). Omit — or send any other value — to get everything accessible. Applied as a post-filter on the full accessible set, so it narrows the response but not the query. |
project_id | string | A project you have edit, full, or owner permission on. Adds that project owner's personal moodboards, each flagged with an owner object and is_mine: false. View-only members and non-members get no extra items. |
Rate limit: 10 requests/minute per user (shared SDK generation bucket) on GET /v1/moodboards and GET /v1/moodboards/:id; 120/min on the create/update/delete routes.
Example
# List all moodboards
curl https://api.kolbo.ai/api/v1/moodboards \
-H "X-API-Key: kolbo_live_..."
# List only your personal moodboards
curl "https://api.kolbo.ai/api/v1/moodboards?scope=personal" \
-H "X-API-Key: kolbo_live_..."Response
{
"success": true,
"moodboards": [
{
"id": "6601a1b2c3d4e5f6a7b8c9d0",
"name": "Soft Studio",
"style_guide": "For product shots. Avoid harsh shadows.",
"thumbnail_url": "https://cdn.kolbo.ai/moodboards/...",
"is_preset": true,
"content_scope": "preset",
"images": ["https://cdn.kolbo.ai/moodboards/ref1.jpg"],
"owner": { "userId": "65ab…", "name": "", "avatar": null },
"is_mine": true,
"created_at": "2025-12-01T00:00:00Z"
}
],
"count": 12
}The content_scope field indicates ownership: personal, preset (system), or organization. owner / is_mine are attribution — for boards surfaced through project_id, owner carries the teammate's userId, name and avatar. is_shared is included when the moodboard has been shared via a public link.
master_prompt is never returned by the list endpoint. The underlying query explicitly
projects it out, so the key is absent from every item here — including for boards you own. Fetch
GET /v1/moodboards/:id when you need the master prompt. You do not need it to apply a
moodboard: pass the id as moodboard_id and the server resolves the prompt itself.
Get Moodboard
Retrieve a single moodboard by ID.
Endpoint
GET /api/v1/moodboards/:idAccess is granted when you own the board, when it is a system preset, or when it is scoped to your active organization. Otherwise 403. A malformed id returns 400, a missing one 404.
There is no project_id query parameter on this route. It resolves shared-project access only
from the board's own originating-project tag — and no create path ever writes that tag, so a
teammate's moodboard that you can see in GET /v1/moodboards?project_id=… will still 403 here.
Read name, style_guide, thumbnail_url and images off the list response instead;
master_prompt is unavailable for a teammate's board, but you can still apply the board by
passing its id as moodboard_id on a generation request.
Example
curl https://api.kolbo.ai/api/v1/moodboards/6601a1b2c3d4e5f6a7b8c9d0 \
-H "X-API-Key: kolbo_live_..."Response
{
"success": true,
"moodboard": {
"id": "6601a1b2c3d4e5f6a7b8c9d0",
"name": "Soft Studio",
"master_prompt": "soft studio lighting, clean white background, subtle shadows, product photography aesthetic",
"style_guide": "For product shots. Avoid harsh shadows.",
"thumbnail_url": "https://cdn.kolbo.ai/moodboards/...",
"is_preset": true,
"content_scope": "preset",
"images": ["https://cdn.kolbo.ai/moodboards/ref1.jpg"],
"owner": { "userId": "65ab…", "name": "Dana Levi", "avatar": "https://…" },
"is_mine": true,
"created_at": "2025-12-01T00:00:00Z"
}
}Create a Moodboard
POST /api/v1/moodboardsAccepts JSON or multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Trimmed, 1–100 chars. |
images | array or JSON string | Yes* | 1–15 items. Each is { "type": "url", "url": "…" } (URL must be a valid URI) or { "type": "file", … } paired with an uploaded file part. originalName is optional. Over 15 items returns 400. |
style_guide | string | No | Style notes, max 500 chars, steering the analysis. |
* You may instead upload image files directly as multipart parts and omit images entirely — in that case only name is validated. Uploaded files must have an image/* MIME type, are capped at 10 MB each, and at most 15 files per request.
Account cap: 50 moodboards per user. Creating past that returns 400.
The server uploads/normalizes the images and runs multi-image style analysis to synthesize the master_prompt. Creation is synchronous — the request stays open through the analysis. Success is HTTP 201.
Both validators reject unknown fields. On create, only name, images, and style_guide
are allowed; on a plain JSON update, only name, images, and style_guide. Sending
anything else — including a project_id — returns 400 with
{ "success": false, "error": "Validation error", "details": ["…is not allowed"] }.
The strictness is relaxed only for a files-only multipart create (file parts present and no
images field) and for an update that carries file parts or imageDescriptors; those two paths
ignore extra form fields.
The SDK response is deliberately narrow — { "success": true, "moodboard": { "id", "name", "thumbnail_url", "image_count", "created_at" } }. It does not include master_prompt. Fetch
the board with GET /v1/moodboards/:id if you need it.
Pass the returned id as moodboard_id on generation endpoints.
Update a Moodboard
PUT /api/v1/moodboards/:id| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Trimmed, 1–100 chars. Omit to keep the current name; an empty or whitespace-only string is a 400, not a no-op. |
images | array | No | Full replacement set, 1–15 items, same item shape as create. Providing it replaces the whole set (old image records are deleted first) and re-runs the style analysis. |
imageDescriptors | array or JSON string | No | Alias for images, accepted as a stringified JSON array for multipart/form-data clients. When both are sent, imageDescriptors wins. Malformed JSON returns 400. |
style_guide | string | No | Max 500 chars. Empty string is allowed and clears it. |
Changing style_guide alone also regenerates master_prompt — the server re-runs the style
analysis over the board's existing images with the new guide. Sending only a name change skips
analysis entirely.
Owner only — a board you do not own returns 403, a missing one 404, and system presets (is_preset: true) return 403. Same narrow response shape as create, minus image_count: the update path returns the moodboard document, which does not carry the image list, so that key is omitted.
Delete a Moodboard
DELETE /api/v1/moodboards/:idPermanent, owner only; system presets return 403. Underlying image files remain in storage. Response: { "success": true, "id": "…" }.
Errors on the CRUD routes
The create / update / delete wrappers flatten every failure to
{ "success": false, "error": "…", "details": … }. There is no separate code field — error
carries whichever value the underlying controller supplied:
| Route | Status | error |
|---|---|---|
| create | 400 | The 50-board cap message (checked first), "Validation error" with details = the Joi messages array, or — when images is a JSON string that slips past Joi — "At least one image is required" / "Maximum 15 images allowed per moodboard" from the controller |
| create | 500 | One of the machine codes UPLOAD_FAILED, AI_ANALYSIS_FAILED, DATABASE_ERROR, UNEXPECTED_ERROR |
| update / delete | 403 | "Access denied. You don't have permission to modify this moodboard." or "System presets cannot be modified or deleted" |
| update / delete | 404 | "Moodboard not found" |
| update | 400 | "Validation error", or "Invalid imageDescriptors format - must be valid JSON" when imageDescriptors is a string that does not parse |
| update / delete | 500 | The raw exception message — not a machine code |
Only create emits the four uppercase codes; update and delete surface the underlying exception text
instead. Branch on the HTTP status, not on error.
A malformed (non-ObjectId) :id on PUT / DELETE is not a 400. The ownership middleware
runs findById on the raw value, the cast throws, and the request comes back as 500
"Failed to verify moodboard ownership". Only GET /v1/moodboards/:id validates the id shape up
front and returns 400.
MCP Tools
The matching @kolbo/mcp tools are list_moodboards, get_moodboard, create_moodboard,
update_moodboard, and delete_moodboard. They take image URLs only (image_urls) and wrap
them into { "type": "url", "url": … } descriptors for you — for local files, call upload_media
first and pass the returned URLs.
Using a Moodboard
Pass moodboard_id to any generation request to apply a moodboard's style. The moodboard's master_prompt is automatically combined with your prompt.
Applying a moodboard forces the prompt-enhancement pass on, even if you set
enhance_prompt: false — the master prompt has to be synthesized with your intent rather than
concatenated onto it. moodboard_id must be a valid ObjectId or the request returns 400.
#Name mentions
Instead of moodboard_id, you can write #Name in the prompt itself and the server resolves it to a
moodboard you can access. Rules enforced by the parser:
- An explicit
moodboard_idin the body always wins over a#mention. - At most one moodboard is applied per generation. If several
#mentions resolve, the first by position wins and the rest are stripped. - A
#tokenthat does not resolve to an accessible moodboard is left completely untouched in the prompt (so hashtags and markdown headings are safe). Purely numeric tokens (#2026) and##-style markdown headings are never treated as mentions. - Confirmed mentions are stripped from the prompt text — the moodboard is applied through its
master_prompt, not by leaving the tag in the sentence. - Like
@, the#capture runs past single spaces, so#Brand Mood make her smilefirst captures the whole phrase. When that fails to match, the server retries progressively shorter word prefixes (longest first) and trims the stripped span back to#<actual name>, leaving the rest of the sentence intact. Single-token moodboard names avoid the round trip.
Image Generation
curl -X POST https://api.kolbo.ai/api/v1/generate/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "A woman walking through a market",
"moodboard_id": "6601a1b2c3d4e5f6a7b8c9d0",
"aspect_ratio": "9:16"
}'Image Editing
curl -X POST https://api.kolbo.ai/api/v1/generate/image-edit \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Replace the background with a sunset",
"source_images": ["https://example.com/photo.jpg"],
"moodboard_id": "6601a1b2c3d4e5f6a7b8c9d0"
}'Creative Director — All Scenes
Apply the same moodboard across every scene:
curl -X POST https://api.kolbo.ai/api/v1/generate/creative-director \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Premium sneaker campaign",
"scene_count": 4,
"moodboard_id": "6601a1b2c3d4e5f6a7b8c9d0",
"aspect_ratio": "16:9"
}'Creative Director — Per-Scene Moodboards
Use different moodboards per scene with moodboard_ids (array, one ID per scene):
curl -X POST https://api.kolbo.ai/api/v1/generate/creative-director \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Brand campaign across different moods",
"scene_count": 3,
"moodboard_ids": [
"id-soft-studio",
"id-dark-cinematic",
"id-outdoor-golden"
]
}'JavaScript Example
const API_KEY = "kolbo_live_YOUR_API_KEY";
const BASE = "https://api.kolbo.ai/api";
async function main() {
// 1. List available moodboards
const mbRes = await fetch(`${BASE}/v1/moodboards`, {
headers: { "X-API-Key": API_KEY }
});
const { moodboards } = await mbRes.json();
// 2. Pick the first preset moodboard
const moodboard = moodboards.find((m) => m.is_preset);
if (!moodboard) {
console.log("No preset moodboards found.");
return;
}
console.log("Using moodboard:", moodboard.name);
// 3. Generate an image with the moodboard applied
const genRes = await fetch(`${BASE}/v1/generate/image`, {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
prompt: "A coffee cup on a wooden table",
moodboard_id: moodboard.id,
aspect_ratio: "1:1"
})
});
const { poll_url } = await genRes.json();
// 4. Poll until the generation completes
let result;
do {
await new Promise((r) => setTimeout(r, 3000));
const status = await fetch(`${BASE}${poll_url}`, {
headers: { "X-API-Key": API_KEY }
});
result = await status.json();
} while (result.state === "processing");
console.log("Image URLs:", result.result.urls);
}
main();Python Example
import time
import requests
API_KEY = "kolbo_live_YOUR_API_KEY"
BASE = "https://api.kolbo.ai/api"
HEADERS = {"X-API-Key": API_KEY}
# 1. List available moodboards
mb_res = requests.get(f"{BASE}/v1/moodboards", headers=HEADERS)
moodboards = mb_res.json()["moodboards"]
# 2. Pick the first preset moodboard
moodboard = next((m for m in moodboards if m.get("is_preset")), None)
if not moodboard:
print("No preset moodboards found.")
exit()
print("Using moodboard:", moodboard["name"])
# 3. Generate an image with the moodboard applied
gen_res = requests.post(
f"{BASE}/v1/generate/image",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"prompt": "A coffee cup on a wooden table",
"moodboard_id": moodboard["id"],
"aspect_ratio": "1:1",
},
)
poll_url = gen_res.json()["poll_url"]
# 4. Poll until the generation completes
while True:
time.sleep(3)
status = requests.get(f"{BASE}{poll_url}", headers=HEADERS).json()
if status["state"] != "processing":
break
print("Image URLs:", status["result"]["urls"])Tips
- System presets (
is_preset: true) are available to all users — they're a good starting point. - The moodboard
master_promptis folded into your prompt by the enhancement pass, so it influences creative direction without overriding your subject. - Moodboards combine well with Visual DNA — use Visual DNA for character/product identity and a moodboard for overall style.
- For Creative Director,
moodboard_ids[i]is used for sceneiwhen present, otherwise the request falls back tomoodboard_idfor that scene. Both are validated as ObjectIds.