API reference

A JSON REST API over HTTPS. One error shape, one job envelope, and a stable error.code on everything that can go wrong.

Base URL: https://gateway.market4me.ai//api/v1v1.0.0

Authentication

Every request needs an API key. Create one in the dashboard under Settings → API & integrations. The key is shown once — we store only a SHA-256 hash and genuinely cannot recover it, so if you lose it, revoke it and make another.

bash
# preferred
curl https://gateway.market4me.ai//api/v1/me -H "Authorization: Bearer m4m_live_..."

# equivalent, for clients that only support an API-key header
curl https://gateway.market4me.ai//api/v1/me -H "X-API-Key: m4m_live_..."

Keys are prefixed m4m_live_ or m4m_test_. A key inherits exactly the access of the person who created it — the same workspace, the same role — and can never exceed it.

A key spends real credits. m4m_test_ keys are not a sandbox: they bill identically and exist only so you can revoke a staging credential without touching production. Treat every key like a password, and give autonomous agents a key with a spending ceiling.

Scopes

A key created with no scopes has full access — the common case. Selecting scopes makes it a strict allow-list, which is what you want for a key you hand to a third party or an agent.

An unrecognised scope is rejected with 400 rather than ignored. A typo like jobs:list must never quietly produce an all-powerful key.

ScopeGrants
brands:readRead brands and their brand intelligence
brands:writeCreate and update brands, trigger analysis
generate:videoGenerate video (charges credits)
generate:imageGenerate and edit images (charges credits)
generate:audioGenerate voiceover and clone voices (charges credits)
generate:contentGenerate scripts, captions, carousels, strategy
jobs:readRead job status and results
assets:readList generated assets and available models
assets:writeMint upload URLs to put your own media into the account
credits:readRead credit balance, pricing and usage
analytics:readRead performance analytics
webhooks:readList webhook endpoints and their delivery health
webhooks:writeCreate and delete webhook endpoints

GET /v1/scopes tells you which scopes the calling key actually holds.

Errors

Every failure returns the same shape. Branch on error.code — it is stable. error.message is written to be read by a person or a model and says what to do next, but it is not a contract.

json
{
  "error": {
    "type": "insufficient_credits_error",
    "code": "insufficient_credits",
    "message": "This operation costs 350 credits but the account has 120. Top up at …/dashboard/billing.",
    "credits_required": 350,
    "credits_available": 120,
    "request_id": "req_8f21c0…",
    "doc_url": "https://market4me.ai/docs/api#insufficient_credits"
  }
}

Codes worth handling explicitly

HTTPcodeWhat to do
401missing_api_keyNo key was sent. Add the Authorization header.
401invalid_api_keyThe key is wrong or unknown. Do not retry.
401revoked_api_keySomeone revoked it. Issue a new one.
403insufficient_scopeThe key lacks the scope this route needs. Create a key with it — you cannot widen an existing key.
400missing_required_parameterRead error.param for which one.
400invalid_enumThe message lists the allowed values.
400brand_not_analysedRun POST /brands/{id}/analyze, wait for complete, retry.
402insufficient_creditsNot enough balance. Retrying cannot succeed — tell the user.
402subscription_requiredBuying credits will not fix this. The endpoint needs an active subscription.
402key_credit_limit_reachedThis key hit its own spending ceiling. Raise it or use another key.
409idempotency_key_reusedSame key, different body. Use a new key for a new request.
429rate_limit_exceededBack off for Retry-After seconds.
502generation_failedThe generation failed and credits were refunded. Safe to retry once.
404not_foundMissing — or not visible to this key. The two are deliberately indistinguishable.

Every response carries an X-Request-Id header, echoed as error.request_id. Quote it in support requests.

Rate limits

Limits are per key, per minute — not per IP, so your server-to-server integration is not throttled by traffic from elsewhere in your company. The default is 120 requests/minute and you can set it per key when you create it.

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in it.
X-RateLimit-ResetUnix timestamp when the window resets.
Retry-AfterOn a 429 only — seconds to wait.

Rate limits are about fairness, not billing. Credits are the billing control, and a rate limit never charges you.

Idempotency

Send an Idempotency-Key header on any POST. Replaying the same key returns the original response instead of doing the work again — so a timeout, a retry or a duplicated queue message cannot charge you twice.

bash
curl https://gateway.market4me.ai//api/v1/videos \
  -H "Authorization: Bearer m4m_live_..." \
  -H "Idempotency-Key: 7f3c1e90-2b44-4a1e-9d77-8c0e2a1b5f60" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a neon sign flickering on at dusk"}'
  • Use a fresh UUID per logical operation, and reuse it across retries of that operation.
  • A replayed response carries Idempotent-Replay: true.
  • Same key with a different body is a 409 — that is a bug on the caller's side, and answering with the wrong cached response would be worse than failing.
  • Keys are remembered for 24 hours, and are scoped to the API key that used them.
  • Only successful responses are cached. A failure can always be retried with the same key.

Jobs

Short generations (clips, images, speech, avatars) return the finished asset directly. Long ones — /brand-videos, /films — return a job, and every job in the API looks the same regardless of which engine is doing the work.

json
{
  "id": "job_9f2c1a7e4b…",
  "object": "job",
  "kind": "brand_video",
  "status": "running",          // queued → running → succeeded | failed | cancelled
  "progress": 45,
  "credits": 120,
  "output": null,               // populated once succeeded
  "error": null,                // why it failed; credits are refunded automatically
  "metadata": { "your": "data" },
  "poll_url": "https://gateway.market4me.ai//api/v1/jobs/job_9f2c1a7e4b…",
  "is_terminal": false,
  "created_at": "2026-07-21T10:04:11Z",
  "completed_at": null
}

On success, output.url is the finished asset — a stable, shareable link you can hand straight to a user. output.download_url is a direct download that expires after 24 hours.

Waiting for a job

Three ways, best first:

  1. Webhooks — be told. Best for a real integration. See below.
  2. POST /jobs/{id}/wait — one blocking call that returns as soon as the job is terminal (up to timeout_s, max 300). Ideal for scripts and AI agents, which have no reliable timer of their own.
  3. Polling GET /jobs/{id} — works, but you are writing a loop that the other two make unnecessary.
bash
# blocks until finished, or returns timed_out:true so you can call again
curl -X POST https://gateway.market4me.ai//api/v1/jobs/job_9f2c1a7e4b/wait \
  -H "Authorization: Bearer m4m_live_..." \
  -H "Content-Type: application/json" \
  -d '{"timeout_s": 120}'

GET /jobs/{id} also accepts the raw UUID of work started in the Market4Me dashboard, so a user can hand you a render id and you can track it.

Webhooks

Register an endpoint and Market4Me POSTs to it when a job finishes, instead of you polling.

bash
curl https://gateway.market4me.ai//api/v1/webhooks \
  -H "Authorization: Bearer m4m_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://yourapp.com/hooks/market4me","events":["job.succeeded","job.failed"]}'

The response contains a secret — shown once. Store it; you need it to verify deliveries.

Events

EventFires when
job.runningA job started processing
job.succeededA job finished successfully and its output is ready
job.failedA job failed (credits are refunded automatically)
brand.analysis.completedBrand analysis finished and brand intelligence is available

Omit events to receive everything.

Verifying a delivery

Each request carries M4M-Signature: t=<unix>,v1=<hex>, where the digest is HMAC-SHA256 over {timestamp}.{raw body}. Check the digest and the age — the timestamp is inside the signed payload precisely so a captured request cannot be replayed later.

javascript
import crypto from 'node:crypto'

// IMPORTANT: verify against the RAW body, before any JSON parsing.
export function verify(rawBody, header, secret) {
  const t  = /t=(\d+)/.exec(header)?.[1]
  const v1 = /v1=([a-f0-9]+)/.exec(header)?.[1]
  if (!t || !v1) return false

  // Reject anything older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false

  const expected = crypto.createHmac('sha256', secret)
    .update(`${t}.${rawBody}`).digest('hex')

  // Constant-time compare — a plain === leaks the digest byte by byte.
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}
python
import hmac, hashlib, time, re

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    t  = re.search(r"t=(\d+)", header)
    v1 = re.search(r"v1=([a-f0-9]+)", header)
    if not t or not v1:
        return False
    if abs(time.time() - int(t.group(1))) > 300:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{t.group(1)}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(v1.group(1), expected)

Delivery behaviour

  • Answer 2xx within 10 seconds. Do the real work asynchronously.
  • Failures retry 6 times with backoff over roughly 2 hours.
  • An endpoint that fails 20 times consecutively is disabled — check GET /webhooks if deliveries stop.
  • Redirects are not followed. Register the final URL.
  • Endpoints must be public https. Private, loopback and link-local addresses are rejected, and re-checked before every delivery.
  • POST /webhooks/{id}/test sends a test event so you can verify your endpoint without spending credits on a real render.

Deliveries are at-least-once. Every event carries a unique id — de-duplicate on it rather than assuming exactly-once.

Models

Generation endpoints take an optional model, expressed as a quality/price tier. Omit it and you get a sensible default — most callers should.

TierFor
economyDrafts, tests, high-volume work. Cheapest.
fastQuick turnaround at a moderate price.
standardThe balanced default for most marketing video.
premiumHigher fidelity motion and detail.
ultraBest general-purpose quality.
max4K output. Most expensive.

Images use economy, standard and premium. GET /v1/models returns the live catalogue with per-tier prices, so you can pick on cost rather than guessing.

All endpoints

Generated from the OpenAPI spec this API actually serves, so it cannot drift from the implementation.

Account

GET/v1/me

Call this FIRST. It verifies the key and returns everything needed to plan work: credit balance, subscription plan, which brands exist (with their analysis status), what this key is allowed to do, and which endpoints are gated behind a subscription.

GET/v1/creditscredits:read

Credit balance and recent transactions

ParameterTypeNotes
limitqueryinteger
POST/v1/estimatecredits:read

Returns the exact credit cost of an operation and whether the account can afford it. Use this before any generation you have not priced before, and to compare options (a 1080p 10s clip vs a 720p 5s one) without spending anything. Free.

ParameterTypeNotes
operationstring
One of: video image speech avatar motion_sync ugc brand_video content
duration_snumber
qualitystring
modelstring
modestring
textstringFor operation=speech — cost scales with length.
operationsarrayPrice several at once and get a total. Use this to budget a whole campaign in one call.
GET/v1/usagecredits:read

API usage and spend, grouped by endpoint

ParameterTypeNotes
daysqueryinteger

Brands

GET/v1/brandsbrands:read / brands:write

List brands

POST/v1/brandsbrands:read / brands:write

Creates a brand. If `website_url` is given, brand analysis starts immediately — it crawls the site and builds the brand brain that makes later generations on-brand. Analysis takes 30–90s; poll GET /v1/brands/{id} until analysis_status is "complete".

ParameterTypeNotes
namerequiredstring
website_urlstringStrongly recommended — this is what makes generations on-brand.
nichestring
descriptionstring
GET/v1/brands/{brand_id}brands:read / brands:write

Get one brand

PATCH/v1/brands/{brand_id}brands:read / brands:write

Update a brand

ParameterTypeNotes
namestring
website_urlstring
nichestring
descriptionstring
tone_guidelinesstring
target_audiencestring
products_servicesstring
POST/v1/brands/{brand_id}/analyzebrands:write

Re-crawls the brand website and rebuilds the brand brain. Use after the site changes, or if analysis_status is "failed". Runs in the background — poll GET /v1/brands/{id}.

GET/v1/brands/{brand_id}/intelligencebrands:read

The structured understanding of the business: tone of voice, audience, products, USP, brand story, key messages, content pillars and themes. Useful when you want to reason about the brand yourself (writing copy, planning a campaign) rather than relying on automatic grounding.

Generation

POST/v1/videosgenerate:video

The simplest useful call in the API. Returns the finished video URL synchronously — no polling. Automatically grounded in the brand (set use_brand:false to opt out). For a multi-scene narrated film use POST /films; for the full brand pipeline with scripted voiceover and captions use POST /brand-videos.

ParameterTypeNotes
promptrequiredstringWhat should happen on screen. Describe the shot, not the marketing goal.
brand_idstringOmit to use the default workspace brand.
duration_snumberCost scales with this.
aspect_ratiostring
One of: 9:16 16:9 1:1 4:5 3:4 4:3
qualitystring
One of: 480p 720p 1080p 4k
modelstringQuality/price tier. Omit for the default. GET /models lists them with prices.
One of: economy fast standard premium ultra max
image_urlstringAnimate this image instead of generating from scratch.
image_urlsarrayUp to 9 reference images.
use_brandboolean
metadataobject
POST/v1/imagesgenerate:image

Synchronous. mode=generate makes a new image from a prompt; mode=edit changes an existing one (needs image_url); mode=face-swap places a face into a scene (needs image_url and face_image_url). Output URLs can be fed straight into POST /videos as image_url.

ParameterTypeNotes
promptstring
modestring
One of: generate edit face-swap
brand_idstring
image_urlstring
face_image_urlstring
qualitystring
One of: low medium high
modelstringQuality/price tier. Omit for the default.
One of: economy standard premium
sizestring
use_brandboolean
POST/v1/speechgenerate:audio

Generates a voiceover audio file. Cost scales with character count. The returned URL can be passed to POST /avatars as audio_url.

ParameterTypeNotes
textrequiredstring
voicestringVoice id. Omit for the brand default.
brand_idstring
POST/v1/avatarsgenerate:video

Takes a portrait image plus words and returns a video of that person saying them, lip-synced. Give either `text` (we generate the voice) or `audio_url` (your own track).

ParameterTypeNotes
image_urlrequiredstringPortrait of the person who should speak.
textstring
audio_urlstring
voicestring
resolutionstring
One of: 480p 720p 1080p
brand_idstring
POST/v1/contentgenerate:content

Brand-grounded copy. content_type short_video/reel/tiktok returns a script with hooks; carousel returns slides; caption returns a post caption with hashtags. Cheap and fast — use it to draft before spending credits on a render.

ParameterTypeNotes
content_typerequiredstring
One of: short_video reel tiktok carousel caption
topicstring
platformsarray
custom_instructionsstring
slides_countinteger
brand_idstring
POST/v1/brand-videosgenerate:video

The complete marketing-video pipeline, not a single clip. Give a `topic` and it writes the script itself. ASYNCHRONOUS: returns a job — poll GET /jobs/{id} or call POST /jobs/{id}/wait. Requires the brand to have been analysed.

ParameterTypeNotes
topicstringWhat the video is about. We write the script.
scriptstringYour own script, instead of a topic.
brand_idstring
duration_snumber
platformstring
One of: tiktok instagram youtube linkedin facebook
aspect_ratiostring
metadataobject
POST/v1/filmsgenerate:video

The most capable generation in the product: an idea becomes a storyboarded, multi-scene, narrated film. ASYNCHRONOUS — returns a job. REQUIRES AN ACTIVE SUBSCRIPTION; buying credits does not unlock it, so a 402 with code subscription_required cannot be fixed by topping up.

ParameterTypeNotes
idearequiredstring
duration_snumber
aspect_ratiostring
stylestring
One of: cinematic cartoon
voiceoverboolean
brand_idstring
POST/v1/ugc/scriptgenerate:content

Writes a multi-scene creator-style ad script. FREE — no credits. Iterate here with the user, then pass the scenes to POST /ugc to render.

ParameterTypeNotes
productstring
anglestring
ugc_typestring
One of: testimonial unboxing demo review story
duration_snumber
brand_idstring
POST/v1/ugcgenerate:video

Render a UGC ad from a scene script

ParameterTypeNotes
scenesrequiredarray
image_urlstringThe creator/actor portrait.
qualitystring
One of: 480p 720p 1080p
brand_idstring
POST/v1/transcriptionsgenerate:content

Transcribe a video, or burn subtitles into it

ParameterTypeNotes
video_urlrequiredstring
modestring
One of: transcript subtitles
brand_idstring

Jobs

GET/v1/jobsjobs:read

List jobs

ParameterTypeNotes
statusquerystring
One of: queued running succeeded failed cancelled
kindquerystring
brand_idquerystring
limitqueryinteger
GET/v1/jobs/{job_id}jobs:read

Poll until is_terminal is true. On success, `output.url` is the finished asset. Prefer POST /jobs/{id}/wait, which blocks instead of making you poll.

POST/v1/jobs/{job_id}/waitjobs:read

Long-polls, up to timeout_s (default 60, max 300). Returns as soon as the job is terminal. If it is still running when the timeout hits you get 200 with timed_out:true — call again to keep waiting. This is the preferred way to await a job: no polling loop, no wasted turns.

ParameterTypeNotes
timeout_sinteger

Assets

GET/v1/assetsassets:read

Everything this account has produced. This is where a render that outlived its request window turns up.

ParameterTypeNotes
kindquerystring
One of: video image audio
brand_idquerystring
limitqueryinteger
POST/v1/uploadsassets:write

Two steps: call this, then PUT the bytes to `upload_url` with the same Content-Type. Pass the returned `asset_url` to any endpoint that takes image_url / video_url / audio_url. This is how you bring a product photo or an existing clip into a generation.

ParameterTypeNotes
filenamerequiredstring
content_typerequiredstring
brand_idstring
GET/v1/modelsassets:read

List available generation models

Webhooks

GET/v1/webhookswebhooks:read / webhooks:write

List webhook endpoints

POST/v1/webhookswebhooks:read / webhooks:write

Be told when a job finishes instead of polling. The signing secret is returned ONCE — store it. Each delivery carries `M4M-Signature: t=<unix>,v1=<hmac-sha256 of "t.body">`.

ParameterTypeNotes
urlrequiredstringhttps required (http allowed for localhost).
eventsarrayOmit for all events.
descriptionstring
DELETE/v1/webhooks/{webhook_id}webhooks:write

Delete a webhook endpoint

POST/v1/webhooks/{webhook_id}/testwebhooks:write

Verifies the endpoint is reachable and answers 2xx, without spending credits on a real render first.

GET/v1/webhooks/events

List the event types you can subscribe to


Driving this from an AI agent instead? The MCP server wraps all of this as tools for Claude, ChatGPT and Cursor.

Market4Me API v1Get a key · API · MCP