Kolbo.AIKolbo.AI Docs
Developer API

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/moodboards

Query Parameters

ParameterTypeDescription
scopestringFilter 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_idstringA 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/:id

Access 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/moodboards

Accepts JSON or multipart/form-data.

FieldTypeRequiredDescription
namestringYesTrimmed, 1–100 chars.
imagesarray or JSON stringYes*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_guidestringNoStyle 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
FieldTypeRequiredDescription
namestringNoTrimmed, 1–100 chars. Omit to keep the current name; an empty or whitespace-only string is a 400, not a no-op.
imagesarrayNoFull 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.
imageDescriptorsarray or JSON stringNoAlias for images, accepted as a stringified JSON array for multipart/form-data clients. When both are sent, imageDescriptors wins. Malformed JSON returns 400.
style_guidestringNoMax 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/:id

Permanent, 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:

RouteStatuserror
create400The 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
create500One of the machine codes UPLOAD_FAILED, AI_ANALYSIS_FAILED, DATABASE_ERROR, UNEXPECTED_ERROR
update / delete403"Access denied. You don't have permission to modify this moodboard." or "System presets cannot be modified or deleted"
update / delete404"Moodboard not found"
update400"Validation error", or "Invalid imageDescriptors format - must be valid JSON" when imageDescriptors is a string that does not parse
update / delete500The 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_id in 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 #token that 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 smile first 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_prompt is 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 scene i when present, otherwise the request falls back to moodboard_id for that scene. Both are validated as ObjectIds.