API referenceOne key. Every format.Say what you want; prompts compile server-side.

Everything my tools do goes through this REST API. It's a semantic API: you send structured intent; a graphic spec, an edit instruction, a script; and the server compiles the actual model prompts, runs the generation on the image, video, and voice models behind me, and meters your credits per call.

Authentication

Every authenticated request carries your API key; either as a bearer token or an x-api-key header. Keys are minted on the API keys page (or via the device-pairing flow below) and look like ma_live_ followed by 40 hex characters.

Headers
Authorization: Bearer ma_live_<40 hex chars>
# — or —
x-api-key: ma_live_<40 hex chars>

Base URL: the MARKETING_AGENT_API_URL you deploy the API under (e.g. https://heymarta.ai/api). Examples below use $API.

Rate limits: 300 requests per minute per IP across the API (device pairing is tighter: 10/min to start, 60/min to poll). If the API runs on a sleeping free instance, the first request can take about a minute while it wakes; retry rather than give up.

The credits model

1 credit = $0.01. New accounts start with a 450-credit welcome grant. Every generation is metered per call: credits are reserved when a job starts, then captured for what was actually used (or released if the job errors). Your ledger shows all of it; topup, reserve, capture, release.

  • graphic / edit / end-card40 credits · ≈ $0.40
  • logo (raster/vector)15 credits · ≈ $0.15
  • image → video12 credits/sec · ≈ $0.12/s
  • lip-sync40 credits · ≈ $0.40
  • narration12 credits/1k chars · ≈ $0.12
  • music bed5 credits · ≈ $0.05
  • upload1 credit · ≈ $0.01

Image → video bills per second (12 credits/s), doubling at 1080p; narration bills per 1,000 characters (min 2). A full UGC-style video (stills + motion + narration + lip-sync + music) sums to roughly 315 credits. Insufficient balance returns 402.

Semantic by design

There is no raw model passthrough. You describe what you want; a typed graphic spec, an instruction, a script; and the server compiles the model prompt, constructs every model input itself, prices the call up front, and returns the finished asset. The prompt compiler never ships to clients, and no endpoint accepts arbitrary model parameters: each generation route has a fixed, priced envelope. Failed jobs release their reservation; you only pay for what completes.

Endpoints

GET/v1/health

Auth: none

Health check

Liveness probe. Returns immediately (once a sleeping instance is awake).

Response 200
{ "ok": true }
curl
curl $API/v1/health
GET/v1/me

Auth: API key

Who am I

Returns the account behind the key, its live balance, and the current price table; per-tool estimates plus the per-call prices of the underlying generation kinds.

Response 200
{
  "email": "you@example.com",
  "balance": 450,
  "prices": {
    "generate_graphic": 40,
    "generate_logo": 15,
    "edit_image": 40,
    "animate_still": 72,
    "generate_ugc": 315
  },
  "endpoint_prices": [
    { "label": "image generate/edit", "credits": 40 },
    { "label": "logo (raster/vector)", "credits": 15 },
    { "label": "image-to-video (per second; ×2 at 1080p)", "credits": 12 },
    { "label": "voiceover (per 1k chars, min 2)", "credits": 12 },
    { "label": "lipsync", "credits": 40 },
    { "label": "music", "credits": 5 }
  ]
}
curl
curl -H "Authorization: Bearer $KEY" $API/v1/me
GET/v1/credits/balance

Auth: API key

Credit balance

The current balance, computed from the ledger.

Response 200
{ "balance": 450 }
curl
curl -H "Authorization: Bearer $KEY" $API/v1/credits/balance
GET/v1/credits/history

Auth: API key

Ledger history

The append-only ledger, newest first. Kinds: topup, reserve, capture, release.

Response 200
{
  "entries": [
    {
      "ts": "2026-07-06T12:00:00Z",
      "kind": "capture",
      "amount": 40,
      "job_id": "8b7f…",
      "note": "image generate/edit"
    },
    { "ts": "…", "kind": "topup", "amount": 450, "job_id": null, "note": "welcome credits" }
  ]
}
curl
curl -H "Authorization: Bearer $KEY" $API/v1/credits/history
GET/v1/credits/packs

Auth: API key

Credit packs

The purchasable packs, as shown on the dashboard.

Response 200
{
  "packs": {
    "starter": { "credits": 500,  "usd": 5,  "label": "Starter — 500 credits" },
    "studio":  { "credits": 1200, "usd": 10, "label": "Studio — 1,200 credits (20% bonus)" },
    "agency":  { "credits": 2600, "usd": 20, "label": "Agency — 2,600 credits (30% bonus)" }
  }
}
curl
curl -H "Authorization: Bearer $KEY" $API/v1/credits/packs
POST/v1/credits/checkout

Auth: API key

Buy credits

Creates a Stripe checkout session (test mode) for a pack and returns the hosted payment URL. Credits land in your ledger via a signature-verified webhook once payment completes; nothing else to call.

Request body
{ "pack": "studio" }
Response 200
{ "url": "https://checkout.stripe.com/c/pay/cs_test_…" }
curl
curl -X POST $API/v1/credits/checkout \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"pack":"studio"}'
POST/v1/device/start

Auth: none (pairing flow)

Start device pairing

Browser pairing for terminals and agents; a simplified device grant. The flow: start here to get a human-readable code and a verify_url; open that URL in a browser, where the signed-in user sees the same code and approves this terminal; meanwhile, poll with the long poll_token until the key arrives. Knowing the short code alone never yields a key, and the key is delivered exactly once. Codes expire after 10 minutes. Rate limit: 10/min.

Response 200
{
  "code": "MKQ4-T7WZ",
  "poll_token": "<64 hex chars — keep this secret>",
  "verify_url": "https://…/connect?code=MKQ4-T7WZ",
  "expires_in": 600
}
curl
curl -X POST $API/v1/device/start
GET/v1/device/poll

Auth: none (pairing flow)

Poll device pairing

Poll every few seconds with ?token=<poll_token>. Status is one of pending, approved (includes the API key, exactly once; store it immediately), expired, or unknown. Rate limit: 60/min.

Response 200
{ "status": "pending" }
// then, after the user approves in the browser:
{ "status": "approved", "api_key": "ma_live_…" }
curl
curl "$API/v1/device/poll?token=$POLL_TOKEN"
GET/v1/voices

Auth: API key

Narration voices

The curated roster of narration voices, plus the default. Each voice carries a short tone description so an agent can match the brand. Pick an id from here; unknown names revert to the default.

Response 200
{
  "default": "Rachel",
  "voices": [
    { "id": "Rachel", "tone": "warm, calm female — the safe default" },
    { "id": "Brian",  "tone": "deep, resonant male" },
    { "id": "River",  "tone": "calm, neutral / non-binary" }
  ]
}
curl
curl -H "Authorization: Bearer $KEY" $API/v1/voices
GET/v1/estimate

Auth: API key

Estimate a cost

Price a piece of work before doing it. Query params: tool (one of generate_graphic, generate_logo, edit_image, animate_still, generate_ugc) and count.

Response 200
{ "tool": "generate_graphic", "count": 9, "credits": 135, "usd": 1.35 }
curl
curl -H "Authorization: Bearer $KEY" \
  "$API/v1/estimate?tool=generate_graphic&count=9"
POST/v1/uploads

Auth: API key

Upload a file (1 credit)

Upload a local file (say, a product photo) as base64 and get back a URL the models can read. 1 credit per upload; max 30 MB decoded.

Request body
{ "filename": "product.jpg", "data_b64": "<base64-encoded bytes>" }
Response 200
{ "url": "https://…/product.jpg", "charged": 1 }
curl
curl -X POST $API/v1/uploads \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\":\"product.jpg\",\"data_b64\":\"$(base64 -i product.jpg)\"}"
POST/v1/generate/graphic

Auth: API key

Generate a branded graphic (40 credits)

The flagship endpoint. Send a typed spec; what the piece says, how it should feel, and where things sit; and the server compiles it into a model prompt and renders it on the image model. Alternatively, pass a verbatim prompt string instead of a spec. Adding ref_urls (e.g. an uploaded product photo) turns the call into a reference-guided render that keeps the exact real objects.

Request body; the GraphicSpec shape
{
  "spec": {
    "highLevel": "Instagram square announcing a summer tasting menu",
    "style": {
      "aesthetics": "warm editorial minimalism",   // required
      "lighting": "soft morning window light",     // required
      "medium": "graphic_design",                  // optional: graphic_design | illustration |
                                                   //   photograph | 3d_render | painting
      "art_style": "flat shapes, generous whitespace", // optional
      "palette": ["#FBF7F1", "#B5673F", "#33303A"]     // optional hex palette
    },
    "background": "cream paper texture with subtle grain",
    "objects": [                                   // optional placed elements
      { "desc": "a ceramic espresso cup, steam curling", "bbox": [300, 250, 700, 750] }
    ],
    "texts": [                                     // optional text zones
      { "text": "Summer, plated.", "desc": "large elegant serif headline",
        "bbox": [60, 80, 240, 920], "palette": ["#33303A"] }
    ]
  },
  "brand": {                                       // optional brand block woven into the prompt
    "name": "Casa Lumi", "palette": ["#FBF7F1", "#B5673F"],
    "serif": "high-contrast editorial serif", "sans": "rounded humanist sans",
    "ink": "#33303A", "accent": "#B5673F", "voice": "calm, warm, confident"
  },
  "guidance": "keep it airy; no clutter",          // optional free-text steer
  "aspect_ratio": "1:1",                           // e.g. 1:1, 4:5, 9:16
  "resolution": "2K",                              // up to 2K (the priced envelope)
  "output_format": "png",
  "seed": 42,                                      // optional, for reproducibility
  "ref_urls": ["https://…/product.jpg"]            // optional → reference-guided render
}

bbox zones are [y_min, x_min, y_max, x_max], normalized 0–1000; they pin text and objects to layout regions. One image per call; the compiled prompt never leaves the server.

Response 200
{ "url": "https://…/graphic.png", "seed": 42, "charged": 40, "job_id": "8b7f…" }
curl
curl -X POST $API/v1/generate/graphic \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "highLevel": "Instagram square announcing a summer tasting menu",
      "style": { "aesthetics": "warm editorial minimalism",
                 "lighting": "soft morning window light" },
      "background": "cream paper texture with subtle grain",
      "texts": [{ "text": "Summer, plated.",
                  "desc": "large elegant serif headline",
                  "bbox": [60, 80, 240, 920] }]
    },
    "aspect_ratio": "1:1"
  }'
POST/v1/generate/logo

Auth: API key

Generate a logo (15 credits)

Renders a logo from a description. The default path produces a clean vector render (style defaults to vector_illustration); optionally pin brand colors as hex values. Setting "model": "raster" switches to a flat raster render on the general image model instead (40 credits).

Request body
{
  "prompt": "minimal monogram for a coffee studio, letterform C",
  "colors": ["#B5673F", "#33303A"],   // optional hex palette
  "style": "vector_illustration",     // optional
  "size": "square_hd"                 // optional
}
Response 200
{ "url": "https://…/logo.svg", "charged": 15, "job_id": "1c2d…" }
curl
curl -X POST $API/v1/generate/logo \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"minimal monogram for a coffee studio","colors":["#B5673F"]}'
POST/v1/generate/end-card

Auth: API key

Generate an end card (40 credits)

The closing frame for a UGC-style video: a clean 9:16 card carrying your closing lines (brand name, offer, call to action). Pass the lines; the server composes the card prompt and renders it on the image model.

Request body
{ "lines": ["Casa Lumi", "Summer menu — now booking", "casalumi.example"] }
Response 200
{ "url": "https://…/end-card.jpg", "charged": 40, "job_id": "9e0a…" }
curl
curl -X POST $API/v1/generate/end-card \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"lines":["Casa Lumi","Summer menu — now booking"]}'
POST/v1/generate/edit

Auth: API key

Edit an image (40 credits)

Edit one or more existing images by plain-language instruction; relight, restage, swap a background, place a product into a scene. Both fields are required; input images by URL (upload local files first via /v1/uploads).

Request body
{
  "instruction": "place the product on a marble counter in soft daylight",
  "image_urls": ["https://…/product.jpg"],
  "aspect_ratio": "4:5",     // optional
  "resolution": "2K"          // optional, up to 2K
}
Response 200
{ "url": "https://…/edited.png", "charged": 40, "job_id": "4f5a…" }
curl
curl -X POST $API/v1/generate/edit \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"instruction":"place the product on a marble counter",
       "image_urls":["https://…/product.jpg"]}'
POST/v1/generate/i2v

Auth: API key

Animate a still (12 credits/sec, ×2 at 1080p)

Image → video on the video model. Give it a still and (optionally) a motion prompt; get back a clip. duration_sec is clamped to 5–15; resolution is 720p (default) or 1080p. Pricing: 12 credits per second at 720p (a 5s clip ≈ 60); 1080p bills ×2. Requests above 15s or 1080p are rejected.

Request body
{
  "image_url": "https://…/hero.png",
  "prompt": "slow push-in, steam drifting, shallow depth of field",  // optional
  "duration_sec": 5,          // 5–15, billed per second
  "resolution": "720p",       // 720p | 1080p (1080p bills ×2)
  "audio": true               // optional ambient audio; off by default
}
Response 200
{ "url": "https://…/clip.mp4", "charged": 60, "job_id": "7a1b…" }
curl
curl -X POST $API/v1/generate/i2v \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_url":"https://…/hero.png","duration_sec":5}'
POST/v1/generate/lipsync

Auth: API key

Lip-sync a clip (40 credits)

Sync a talking-head clip to a narration track: mouth movement is re-rendered to match the audio. Both URLs are required; typically the video comes from /v1/generate/i2v and the audio from /v1/generate/tts.

Request body
{ "video_url": "https://…/clip.mp4", "audio_url": "https://…/vo.mp3" }
Response 200
{ "url": "https://…/synced.mp4", "charged": 40, "job_id": "3d9c…" }
curl
curl -X POST $API/v1/generate/lipsync \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"video_url":"https://…/clip.mp4","audio_url":"https://…/vo.mp3"}'
POST/v1/generate/tts

Auth: API key

Narration (12 credits / 1k chars, min 2)

Text → narration, with word-level timestamps in the response so you can time captions to the voice. Pick a voice id from /v1/voices; unknown names revert to the default server-side. Up to 2,500 characters per call. Use previous_text / next_text for consistent delivery across a multi-part script.

Request body
{
  "text": "Golden hour at Casa Lumi. The summer menu is here.",
  "voice": "Rachel",          // optional, from /v1/voices
  "stability": 0.45,          // optional, 0–1
  "style": 0.35,              // optional, 0–1
  "speed": 1.0,               // optional
  "language": "es",           // optional ISO 639-1
  "previous_text": "…",       // optional continuity context
  "next_text": "…"
}
Response 200
{
  "audio_url": "https://…/vo.mp3",
  "timestamps": [ { "word": "Golden", "start": 0.0, "end": 0.42 }, … ],
  "charged": 2,
  "job_id": "5b6e…"
}
curl
curl -X POST $API/v1/generate/tts \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Golden hour at Casa Lumi.","voice":"Rachel"}'
POST/v1/generate/music

Auth: API key

Music bed (5 credits)

A background music track from a text brief. duration_sec should match your video length; the server pads it slightly for clean fades (minimum 10s, max ~90s per call).

Request body
{ "prompt": "warm acoustic morning café, light percussion, unhurried", "duration_sec": 20 }
Response 200
{ "audio_url": "https://…/music.mp3", "charged": 5, "job_id": "6c7d…" }
curl
curl -X POST $API/v1/generate/music \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"warm acoustic morning café","duration_sec":20}'

Across all generation endpoints: invalid input returns 400, a missing or bad key 401, insufficient balance 402, and an upstream model error 502; with the reservation released.

New here? Start with the getting-started guide.