Endpoints

Generate ads

POST https://api.dessert.dev/v1/ads/generate Try it →

The main generation endpoint. Submit a creative brief and Dessert returns a delivery_id within milliseconds. The actual ads — copy, rendered statics, animated MP4s — come back through the status endpoint or your webhook a few minutes later.

Every request creates one delivery containing count ad concepts. Each concept is rendered in two aspect ratios (vertical 1080×1920 and square 1080×1350), so a request with count: 3 produces six rendered files.

Headers

HeaderDescription
AuthorizationrequiredBearer dsrt_live_…
Content-Typerequiredapplication/json
Idempotency-Keyoptional A unique string (UUID recommended) that lets you retry safely. We cache the response for 24h and replay it on duplicate keys instead of generating new ads.

Request body

FieldDescription
countintegeroptional Number of ad concepts to generate. Defaults to 1. Each concept produces one vertical + one square render.
formatstringoptional "full" (default) — static + animated video, 15 credits per ad.
"static" — static image only, 5 credits per ad.
brand_idstringoptional The brand to generate against. Omit and we use your account's default brand. Get IDs from GET /v1/brands.
product_idsstring[]optional Restrict generation to specific products. If omitted, Dessert rotates across all active products on the brand.
creativeobjectoptional Creative direction overrides. See creative object below.
in_situbooleanoptional If true, the product is composited into a lifestyle scene via Gemini before the ad is rendered. Defaults to false.
webhook_urlstringoptional HTTPS URL that we POST to when the delivery completes. See Webhooks.

The creative object

FieldDescription
copy_anglestringoptional Forces the headline + body angle. One of: "pain + solution", "benefit outcome", "sensory", "transformation", "ritual", "social proof". Omit to let Dessert rotate.
brand_tonestringoptional Free-form tone override for this request (e.g. "clinical authority", "playful modern"). Falls back to the brand default.
animation_promptsstring[]optional Pin the animation styles to a specific subset. Pass one or more of the 36 production Animation Prompt categories — see the styles reference for the full list.
style_searchstringoptional Natural-language description of the look you want (e.g. "warm honey drip on amber, minimal, slow"). Used only when animation_prompts is empty. Behind the scenes we run an embedding search and pin the top matches as featured styles.
Two ways to constrain style animation_prompts is categorical and exact — use it when you know the named prompt (e.g. "Serum Droplets"). style_search is semantic — use it when you want to describe a vibe. If you set both, animation_prompts wins.

Request examples

A minimal call using only your defaults:

curl
Python
TypeScript
MCP
curl https://api.dessert.dev/v1/ads/generate \
  -H "Authorization: Bearer $DESSERT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "count": 3,
    "format": "full",
    "creative": {
      "copy_angle": "sensory",
      "brand_tone": "clinical authority",
      "animation_prompts": ["Serum Droplets", "Lotion Texture"]
    },
    "webhook_url": "https://yourapp.com/dessert-callback"
  }'
import os, requests

r = requests.post(
    "https://api.dessert.dev/v1/ads/generate",
    headers={
        "Authorization": f"Bearer {os.environ['DESSERT_API_KEY']}",
        "Idempotency-Key": "campaign-q2-2026-batch-01",
    },
    json={
        "count": 3,
        "format": "full",
        "creative": {
            "copy_angle": "sensory",
            "brand_tone": "clinical authority",
            "animation_prompts": ["Serum Droplets", "Lotion Texture"],
        },
        "webhook_url": "https://yourapp.com/dessert-callback",
    },
    timeout=30,
)
r.raise_for_status()
delivery = r.json()
const res = await fetch("https://api.dessert.dev/v1/ads/generate", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.DESSERT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "campaign-q2-2026-batch-01",
  },
  body: JSON.stringify({
    count: 3,
    format: "full",
    creative: {
      copy_angle: "sensory",
      brand_tone: "clinical authority",
      animation_prompts: ["Serum Droplets", "Lotion Texture"],
    },
    webhook_url: "https://yourapp.com/dessert-callback",
  }),
});
const delivery = await res.json();
{
  "tool": "dessert.generate_ads",
  "arguments": {
    "count": 3,
    "format": "full",
    "creative": {
      "copy_angle": "sensory",
      "brand_tone": "clinical authority",
      "animation_prompts": ["Serum Droplets", "Lotion Texture"]
    },
    "webhook_url": "https://yourapp.com/dessert-callback"
  }
}

Response — 202 Accepted

FieldDescription
delivery_idstringOpaque identifier for this delivery. Pass to GET /v1/deliveries/:id.
statusstringAlways "queued" on first response.
request_idstringTracing ID for support — include in any bug reports.
ad_count_expectedintegerHow many concepts will be generated (= your count).
formatstringEcho of the format you requested.
credits_chargedintegerCredits deducted from your balance for this delivery.
credits_balance_afterintegerRemaining credit balance after deduction.
status_urlstringFull URL to poll for delivery progress.
eta_secondsintegerRough completion estimate (180s for static, 480s for full).
replayed_from_idempotency_keybooleanPresent and true only when an Idempotency-Key matched a prior request.
{
  "delivery_id": "recXXXXXXXXXXXXXX",
  "status": "queued",
  "request_id": "req_a1b2c3d4e5f6789012345abc",
  "ad_count_expected": 3,
  "format": "full",
  "credits_charged": 45,
  "credits_balance_after": 205,
  "status_url": "https://api.dessert.dev/v1/deliveries/recXXXXXXXXXXXXXX",
  "eta_seconds": 480
}

Errors

StatusCodeCause
400invalid_countcount was not a positive integer.
400invalid_formatformat was not "full" or "static".
400no_brand_selectedAccount has no default brand and no brand_id was provided. Create one with POST /v1/brands.
402insufficient_creditsNot enough credits and auto-recharge is off or failed. Top up.
404brand_not_foundThe brand_id does not belong to this account.
425brand_onboarding_in_progressBrand scrape is still running. Try again in 15–60 minutes.
502orchestration_failedUpstream orchestrator was unreachable. Safe to retry (with an Idempotency-Key).

See the Errors reference for the full envelope.