Zolt AI
API Documentation← Back to site

REST API

The automation-friendly surface: generate and revise on-brand content, render images/video, publish to X, schedule posts, and score results. Every endpoint below is POST except where noted, requires an X-API-Key header (see Authentication), and resolves your business by name.

Credits

API calls draw Zolt Credits from the same balance as the app — there is no separate API meter. Calls that generate content are billed; calls that only move data around (scheduling, publishing, uploading, reading results) are free. For a carousel or presentation, /api/v1/generate returns the slide plan (the image-generation prompts and captions listed under Asset types), not finished images — render those separately with /api/v1/render-image.

If your balance can't cover a call, it returns 402. Your spend, including which business used what, is on your Credit usage page.

Resolving business and board

Most endpoints take a business name and an optional board name. Names are matched case-insensitively against a normalized slug (lowercased, non-letters/digits collapsed to -), so "Acme Co." matches a business named acme-co. If board is omitted, your business's default board is used (or its first board, if none is marked default).

If your API key is unscoped, business must still name a business your key belongs to — a business that resolves to a different business than your key's own returns 403. A business/board that doesn't resolve at all returns 404.

Asset types and fields

asset_type accepts video, image, carousel, presentation, or landing_page — matched by prefix and case-insensitively, so a value like "Video – Short Form" from a spreadsheet dropdown still resolves to video. Each asset type generates a fixed set of string fields:

asset_typeFields
videohook, script_copy, caption_tiktok, caption_ig, caption_fb, yt_title, yt_description, yt_tags
imagehook, image_generation_prompt, caption_ig, caption_fb, caption_x (≤280), caption_linkedin, caption_threads (≤500), pinterest_title (≤100), pinterest_description (≤500), pinterest_board
carouselhook, slide_prompts (5 newline-separated image prompts), caption_ig, caption_fb, caption_x (≤280), caption_linkedin, caption_threads (≤500)
presentationhook, slide_content (newline-separated "Title — body" slides), caption_linkedin, caption_x (≤280)
landing_pageheadline, subheadline, body_sections, cta_copy, seo_meta_description (≤160)

Idea

POST
/api/v1/idea
Propose a content idea grounded in this board's connected sources.
FieldTypeRequiredDescription
businessstringoptionalBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
business_contextstringoptionalExtra freeform context to steer the idea.
avoid_recentbooleanoptionalSkip ideas similar to recently generated ones on this board.
modelstringoptionalOverride the default model.

Response:

{
  "generationId": "cldx...",
  "idea": "string",
  "angle": "string",
  "why_now": "string",
  "source_tiles": ["string", "..."]
}
502 Bad Gateway{ "error": "Idea generation failed." }

Generate

POST
/api/v1/generate
Generate a new piece of content for one asset type.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
asset_typestringrequiredvideo | image | carousel | presentation | landing_page
ideastringrequiredThe idea or brief to generate from.
hookstringoptionalOptional hook to build around; the model invents one if omitted.
durationnumberoptionalTarget seconds for video scripts (~2.5 words/sec, ±15%).
variantsnumberoptional1–3. Generates multiple takes.
auto_select_bestbooleanoptionalWith variants > 1, score all variants and return only the winner.
modelstringoptionalOverride the default model.

Response shape depends on variants/auto_select_best. Default (single variant) — the asset type's fields are spread at the top level:

{
  "generationId": "cldx...",
  "provider": "anthropic",
  "model": "claude-sonnet-5",
  "hook": "string",
  "script_copy": "string",
  "caption_tiktok": "string",
  "caption_ig": "string",
  "caption_fb": "string",
  "yt_title": "string",
  "yt_description": "string",
  "yt_tags": "string"
}

variants > 1 without auto_select_best:

{
  "generationId": "cldx...",
  "provider": "anthropic",
  "model": "claude-sonnet-5",
  "variants": [ { /* fields */ }, { /* fields */ }, { /* fields */ } ]
}

variants > 1 with auto_select_best: true — every variant is scored, only the winner is returned, with its score merged in:

{
  "generationId": "cldx...",
  "provider": "anthropic",
  "model": "claude-sonnet-5",
  /* ...winning variant's fields... */
  "score": 82,
  "verdict": "string",
  "top_problem": "string",
  "fixes": "string",
  "improved_hook": "string",
  "variantsGenerated": 3,
  "variantsScored": 3
}
400 Bad RequestRequired fields: business, asset_type ('video'|'image'), idea.
502 Bad Gateway{ "error": "..." }

Revise

POST
/api/v1/revise
Rewrite a previous generation's fields given a score and feedback.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
asset_typestringrequiredvideo | image | carousel | presentation | landing_page
original_fieldsobjectoptionalThe original content fields, as an object — or flatten those same fields at the top level instead.
scorenumberrequiredNumeric score for the original (numeric strings accepted).
top_problemstringrequiredThe single biggest issue to fix.
fixesstring[] | stringrequiredArray of fixes, or a newline-separated string.
improved_hookstringoptionalA suggested replacement hook.
durationnumberoptionalTarget seconds, same as Generate.
modelstringoptionalOverride the default model.
revision_countnumberoptionalIf given, the response includes newRevisionCount = revision_count + 1.

Response — the revised fields, spread at the top level, same shape as Generate's single-variant response:

{ "generationId": "cldx...", "provider": "anthropic", "model": "claude-sonnet-5", /* ...revised fields... */ }
400 Bad RequestRequired fields: business, asset_type, score, top_problem, fixes, and either original_fields or the flattened fields.
502 Bad Gateway{ "error": "Revision failed." }

Content engines

POST
/api/v1/content
Write ad copy, a video script, hooks, a blog post or an email, grounded in this board — the same engines as the app's Write menu.
FieldTypeRequiredDescription
kindstringrequiredads | script | hooks | blog | email
briefstringrequiredWhat the content is about (≤4,000 characters).
businessstringoptionalBusiness name to resolve; defaults to the API key's business.
boardstringoptionalBoard name; defaults to the business's default board.
optionsobjectoptionalPer kind — see below. Unknown keys are ignored.
modelstringoptionalOverride the default model.
FieldTypeRequiredDescription
adsoptionsoptionalnetwork: "google_search" (15 headlines ≤30, 4 descriptions ≤90, paths, keywords) | "meta" | "linkedin" (3 variants each); final_url: the landing page.
scriptoptionsoptionalplatform: "tiktok" | "reels" | "shorts" (seconds: 15/30/45/60/90) or "youtube_long" (minutes: 3–30, an outline with chapters).
hooksoptionsoptionalplatform: where they open (free text); count: 5–20, default 10.
blogoptionsoptionalkeyword: the target search keyword; words: 900–1,800, default 1,200.
emailoptionsoptionalemail_type: "single" | "sequence"; count: 3–5 for a sequence; goal: what it is for.

Every platform limit is enforced in code — an over-limit line is rewritten once and, if still too long, cut on a word boundary (never with an ellipsis). The asset is also saved to the board's Outputs zone as a tile, so it can be revised later with its tile_id. Priced per engine and settled on real usage above that minimum, like the app.

Response:

{
  "kind": "ads",
  "title": "Google Search ad — 20% Off Your First Order",
  "tile_id": "cmu...",
  "generation_id": "cmu...",
  "data": { /* the structured asset: headlines, beats, sections, emails... */ },
  "exports": {
    // ads (google_search): google_ads_editor_csv, google_keywords_csv
    // blog: markdown · script (youtube_long): youtube_description · email: plain_text[]
  },
  "quality": { "repaired": 1, "clamped": 0, "problems": [] }
}
400 Bad Request{ "error": "\"kind\" must be one of: ads, script, hooks, blog, email." }
402 Payment Required{ "error": "...", "code": "insufficient_credits", "credits": { "required": 65, "available": 12 } }
502 Bad Gateway{ "error": "Content generation failed." }
POST
/api/v1/content/revise
Change an engine asset in place; the tile keeps its kind, brief and options, and its history.
FieldTypeRequiredDescription
tile_idstringrequiredThe tile_id returned by /api/v1/content (or any engine tile on your boards).
requeststringrequiredWhat to change — "make the headlines punchier", "shorter subject lines" (≤2,000 characters).
modelstringoptionalOverride the default model.

Response — the same shape as /api/v1/content, with the revised asset.

404 Not Found{ "error": "No content tile with that id." }

Score

POST
/api/v1/score
Score an existing piece of content against this board's brand/audience.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
asset_typestringrequiredvideo | image | carousel | presentation | landing_page
(fields)stringrequiredThe asset type's own fields (hook, script_copy, etc.), flattened at the top level — at least one must be present.
{
  "generationId": "cldx...",
  "score": 74,
  "verdict": "string",
  "top_problem": "string",
  "fixes": "string",
  "improved_hook": "string"
}
400 Bad RequestRequired fields: business, asset_type, plus the content fields.
400 Bad RequestNo recognized content fields found in the body for this asset_type.
502 Bad Gateway{ "error": "Scoring failed." }

Score a stored generation

POST
/api/v1/generations/{id}/score
Attach a score directly to an existing generation by id.
FieldTypeRequiredDescription
scorenumberrequiredThe score to store.
{ "ok": true }
400 Bad RequestRequired field: score (number).
404 Not Found{ "error": "Generation not found." }
403 Forbidden{ "error": "This API key is not authorized for this generation." }

Publish to X

POST
/api/v1/publish-x
Post an image + caption directly to X (Twitter).
FieldTypeRequiredDescription
asset_urlstringrequiredA publicly reachable image URL.
caption_xstringrequiredTweet text. Silently truncated to 277 chars + "…" if over 280.
{ "x_post_id": "string", "x_post_url": "string" }
400 Bad RequestRequired fields: asset_url, caption_x.
502 Bad Gateway{ "error": "X publish failed." }

Render image

POST
/api/v1/render-image
Render a real image from a prompt, in one or more aspect-ratio formats.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
image_generation_promptstringrequiredThe image prompt.
formatsstring[] | stringrequiredSubset of 4x5 | 2x3 | 16x9. Comma-separated string also accepted.
{
  "generationId": "cldx...",
  "images": {
    "4x5": "https://app.getzolt.ai/api/uploads/....png",
    "2x3": "https://app.getzolt.ai/api/uploads/....png"
  }
}
400 Bad RequestRequired fields: business, image_generation_prompt, formats.
400 Bad RequestUnsupported format "9x16". Use 4x5, 2x3, or 16x9.
502 Bad Gateway{ "error": "Image render failed." }

Render video

POST
/api/v1/render-video
Start an AI avatar video render job from a script.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
scriptstringrequiredThe video script.
avatar_idnumberoptionalPresenter avatar id. Omit to auto-rotate through your roster.
avatar_typenumberoptionalAvatar type id.
voice_idstringoptionalVoice id override.
aspect_ratiostringoptionalportrait | landscape | square
{ "generationId": "cldx...", "job_id": "string", "status": "processing", "presenter": "string" }
400 Bad RequestRequired fields: business, script.
400 Bad RequestUnsupported aspect_ratio "wide". Use portrait, landscape, or square.
502 Bad Gateway{ "error": "Video render start failed." }

Render video status

POST
/api/v1/render-video-status
Poll a video render job started by Render video.
FieldTypeRequiredDescription
businessstringrequiredBusiness name to resolve.
boardstringoptionalBoard name; defaults to the business's default board.
job_idstringrequiredThe job_id from Render video's response.

One of three shapes, depending on job state:

{ "job_id": "string", "status": "processing" }
{ "job_id": "string", "status": "failed" }
{ "job_id": "string", "status": "completed", "video_url": "string" }
400 Bad RequestRequired fields: business, job_id.
502 Bad Gateway{ "error": "Video status check failed." }

Schedule

POST
/api/v1/schedule
Get best-practice posting times for a given date, per platform.
FieldTypeRequiredDescription
datestringrequiredYYYY-MM-DD (or a full ISO timestamp — only the date part is used).
(fields)stringoptionalAny of the platform caption fields (caption_ig, caption_fb, etc.) — only platforms with a matching field present get a scheduled time back.

Only keys for platforms whose field was present in the request are returned, e.g.:

{
  "tiktok_scheduled_at": "2026-08-03T14:00:00.000Z",
  "instagram_scheduled_at": "2026-08-03T19:00:00.000Z"
}

All times are UTC and reflect fixed best-practice defaults per platform, not audience-specific analytics.

400 Bad RequestRequired fields: date, plus the generated content fields.

Upload a video for Zolt to post

POST
/api/v1/videos
Upload a video; Zolt watches it, writes each platform's caption in your brand's voice, and the posts wait in Posts to approve.

The same thing as Review inbox → Upload a video, without a browser. Send multipart/form-data. Nothing publishes until a person approves the posts. Costs the usual credits for watching a video and writing its captions.

FieldTypeRequiredDescription
filefilerequiredThe video: .mp4, .mov, .webm, .m4v or .mkv, up to 1GB.
platformsstringrequiredComma-separated or a JSON array: instagram, facebook, tiktok, linkedin, x, threads, bluesky, pinterest, youtube. Each needs an account switched on for publishing.
plan_datestringrequiredYYYY-MM-DD in the business's time zone, today or later.
board_idstringoptionalThe board the video joins. Required unless the API key is scoped to one board.
notestringoptionalA steer for the captions: what to stress, what to avoid.
youtube_visibilitystringoptionalpublic, unlisted or private (YouTube only).
synthetic_mediabooleanoptionaltrue if the video shows realistic AI-made people, voices or scenes (YouTube's disclosure).
{ "tile_id": "clx…", "stage": "watching", "platforms": ["instagram", "facebook"], "plan_date": "2026-10-08", "status_url": "/api/v1/videos/clx…" }
400 Bad Requestinstagram has no account switched on for publishing — connect it under Social accounts and tick "Publish here" first.
402 Payment RequiredNot enough credits for media ingestion.
GET
/api/v1/videos/{tile_id}
Where an uploaded video stands.

stage is one of watching, writing, waiting_for_approval, approved, done or failed; posts lists each platform's post and its date.

{ "tile_id": "clx…", "stage": "waiting_for_approval", "platforms": ["instagram", "facebook"], "plan_date": "2026-10-08", "posts": [{ "platform": "instagram", "plan_date": "2026-10-08", "state": "awaiting_review" }] }

Upload media

POST
/api/v1/upload-media
Host a file and get back a public URL — useful for feeding asset_url into Publish to X.

Accepts either multipart/form-data with a file field, or JSON:

FieldTypeRequiredDescription
image_base64stringrequiredBase64-encoded file contents (JSON mode only).
mime_typestringoptionale.g. image/png, image/jpeg, audio/mpeg (JSON mode only).
{ "url": "https://app.getzolt.ai/api/uploads/abc123.png" }
400 Bad RequestRequired field: file (multipart).
400 Bad RequestRequired field: image_base64 (or send multipart/form-data with 'file').

Questions, or found something undocumented? Contact support.