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 (projectId is accepted as an alias). Adds the project's shared moodboards: the project owner's personal boards, boards on the project's cast roster, and boards created inside the project (project_id on create). View-only members, non-members and malformed ids get no extra items. |
Rate limit: 120 requests/minute per user on all five moodboard routes (list, get, create, update, delete). The bucket is shared with the other SDK media-library routes, and failed requests do not count against it.
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: is_mine is true only for a non-preset board you created, which is also the condition for updating or deleting it. System presets and boards another user created (organization boards, cast boards, teammates' boards surfaced through project_id) come back with is_mine: false and owner.userId set to their creator; owner.name and owner.avatar are filled when known (the project owner's boards) and are otherwise "" / null. 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, when it is scoped to your active organization, or when it is shared through a project you have edit/full/owner permission on. Otherwise 403 ("Access denied"). A malformed id returns 400, a missing one 404.
| Query parameter | Type | Description |
|---|---|---|
project_id | string | Optional. A project you have edit, full, or owner permission on (projectId alias). Lets you fetch a teammate's board that the list returns for the same project_id — the project owner's personal boards, cast boards, and boards created in the project. Without it, the route falls back to the board's own originating project (set when the board was created with project_id), and grants access if you have edit-level permission there. |
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. |
project_id | string | No | Create the board inside this project (projectId is accepted as an alias). If you have edit or full permission on the project (owners count as full), the board is tagged to it and every edit-level teammate can list and fetch it by passing the same project_id, and apply it as moodboard_id on generations that run in that project. Without that permission — or with an unknown id — the board is silently created private, not rejected. |
shareWithOrganization | boolean or string | No | true, "true" or "organization" creates the board with content_scope: "organization" in your active organization. Ignored (board stays personal) when you have no active organization. Default: personal. |
* You may instead upload image files directly as multipart parts and omit images entirely — in that case only name and style_guide are validated (other form fields, including project_id and shareWithOrganization, are still honored). 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, style_guide,
project_id / projectId and shareWithOrganization are allowed; on a plain JSON update, only
name, images, and style_guide (a board's project and scope cannot be changed through the
API). Sending anything else — including a project_id on update — 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 returns 400
{ "status": false, "message": "Invalid moodboard ID format" } from the ownership check, before any
lookup runs.
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. create_moodboard also takes an optional project_id, and
list_moodboards / get_moodboard take project_id to reach boards shared through a project.
Note that list_moodboards defaults to scope: "personal" on the MCP side, while the REST route
returns everything accessible when scope is omitted.
Using a Moodboard
Pass moodboard_id on POST /v1/generate/image, POST /v1/generate/image-edit, or POST /v1/generate/creative-director to apply a moodboard's style. These are the only SDK generation routes that read moodboard_id; other routes ignore it. 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.
#Name mentions also work on Elements (POST /v1/generate/elements), which has no moodboard_id field: the resolved board's master_prompt is appended to the prompt after enhancement. The name lookup covers only your own, organization and preset boards — a board you can reach only through a project is not resolved by #Name here. On first-last-frame generation, confirmed #Name mentions are only stripped from the prompt — the moodboard is not applied.
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.