Frekto API

Generate AI-powered social media posts — images and animated videos — via a simple REST API.

Base URL https://api.frekto.ai

How it works

Frekto uses a two-step async pattern — submit a job, then poll for the result.

# 1. Submit a job — returns immediately
POST /generate → { job_id: "job_123" }

# 2. Poll until done — usually 15–90 seconds
GET /jobs/job_123 → { status: "done", output_url: "https://..." }

Authentication

Pass your API key in the Authorization header on every request.

Authorization: Bearer frekto_live_YOUR_KEY
Get an API key — contact the Frekto team or create one from your dashboard at app.frekto.ai.

Rate Limits

10
renders / day — Free
30
renders / day — Paid

When the limit is reached the API returns 429 Too Many Requests. Limits reset at midnight UTC.

POST /generate

Create a render job. Returns a job_id immediately — rendering happens asynchronously.

POST /generate

Request body

FieldTypeRequiredDescription
topicstringrequiredTopic or subject for the post. Max 300 characters.
stylestringoptionalTemplate style key e.g. revelation::cinematic. Omit or pass auto for AI to pick the best fit based on your topic. See styles.
formatstringoptional4:5 (default), 9:16, 1:1
output_formatstringoptionalpng (default) or mp4
asset_urlstringoptionalPublic URL of background image/video. Must allow CORS.
callback_urlstringoptionalWebhook URL to notify on completion

Example request

{
  "topic": "AI is transforming healthcare",
  "style": "revelation::cinematic",
  "format": "4:5",
  "output_format": "mp4"
}

Response

{
  "job_id": "job_1782400942804_abc123",
  "status": "queued"
}

Error responses

StatusErrorCause
401Invalid or missing API keyMissing or wrong Authorization header
400topic is requiredMissing or empty topic
400topic must be 300 characters or lessTopic too long
400format must be one of: 4:5, 9:16, 1:1Invalid format value
400output_format must be one of: mp4, pngInvalid output format
400Unknown style "..."Style key not in the valid list
400style "media::..." requires asset_urlMedia styles need a public image/video URL
400Request body must be valid JSONMalformed request body
422No linkedin account connected. Connect it at app.frekto.ai/settings...No social account — connect one first
⚠️ Social account required

Before any render starts, the API checks that you have a social account connected. If not, you receive a 422 immediately — no job is created, no credits used. Connect accounts at Settings → Social Accounts.

GET /jobs/:id

Poll a render job by ID. Call every 3–5 seconds until status is done or failed.

GET /jobs/:job_id

Status values

queued rendering done failed

Response — done

{
  "id": "job_1782400942804_abc123",
  "status": "done",
  "output_url": "https://pub-xxx.r2.dev/renders/job_abc123.png",
  "error": null,
  "created_at": 1782400942804,
  "completed_at": 1782400978311
}

POST /generate/series

Generate a full content series from a single instruction. The worker plans N posts, renders each one on Railway, then auto-schedules them — all from one API call. Returns a series_id immediately.

POST /generate/series

Request body

FieldTypeRequiredDescription
instructionstringrequiredWhat the series should be about. Max 500 characters.
countnumberoptionalNumber of posts to generate (2–10, default 3)
platformstringoptionallinkedin (default), instagram, twitter
cadencestringoptionalPost spacing: weekdays (default), daily, mwf, linkedin
formatstringoptional4:5 (default), 9:16, 1:1
output_formatstringoptionalmp4 (default) or png
schedule.start_datestringoptionalISO date string for first post. Defaults to tomorrow.
callback_urlstringoptionalWebhook called when all posts are rendered and scheduled

Example request

{
  "instruction": "5 posts on building a personal brand as a developer",
  "count": 3,
  "platform": "linkedin",
  "cadence": "weekdays",
  "output_format": "mp4",
  "schedule": { "start_date": "2026-07-08" },
  "callback_url": "https://yoursite.com/webhook/series"
}

Response

{
  "series_id": "ser_1783200000000_abc12",
  "status": "rendering",
  "total": 3,
  "series_title": "Building a Personal Brand as a Developer"
}

Poll GET /series/:id every 5 seconds to track progress. Each post renders in parallel on Railway — a 3-post series typically completes in 60–90 seconds.

GET /series/:id

Poll a series by ID. Posts appear with URLs as each render completes. When all posts are done the worker automatically inserts them into your scheduled queue.

GET /series/:series_id

Status values

planning rendering done scheduled partial failed

Response — rendering

{
  "series_id": "ser_1783200000000_abc12",
  "status": "rendering",
  "total": 3,
  "completed": 1,
  "series_title": "Building a Personal Brand as a Developer",
  "posts": [
    { "post_number": 1, "topic": "Why your GitHub is your best portfolio", "style": "revelation::panel", "status": "done", "url": "https://pub-xxx.r2.dev/renders/job1.mp4", "schedule_offset_days": 0, "suggested_time": "08:00" },
    { "post_number": 2, "topic": "The 3-step framework for open-source contributions", "style": "framework::index", "status": "rendering", "url": null, "schedule_offset_days": 2, "suggested_time": "09:00" },
    { "post_number": 3, "topic": "How I landed my first dev job by building in public", "style": "journey::chapter", "status": "queued", "url": null, "schedule_offset_days": 4, "suggested_time": "07:00" }
  ]
}

Response — scheduled

When status reaches scheduled, all posts have been rendered and inserted into your publishing queue with the correct timestamps. If you provided a callback_url, it has already been called.

API Keys

POST /api-keys Create key
{ "name": "My App Key" }
⚠️ Save the key immediately — it is only shown once.
GET /api-keys List keys
DEL /api-keys/:id Revoke key

Template Styles

Pass a style key in the style field. Leave empty or pass auto — the AI analyses your topic and picks the most fitting template automatically.

Auto selection — Claude analyses the topic and selects the best template. A stat-heavy topic gets a revelation::cinematic, a quote gets quote::bold, a comparison gets contrast::versus etc.

Styles

auto
AI picks the best template based on your topic

Revelation

revelation::cinematic
Full bleed, giant stat, animated
revelation::statement
Bold opinion, pure conviction
revelation::split
Image top, stat card bottom

Stat

stat::cinematic
Full bleed, giant centred stat
stat::bold
Dark background, display stat
stat::editorial
Left-aligned editorial tone
stat::minimal
Giant number, minimal copy
stat::split
Left panel stat, right panel context

Quote

quote::bold
Words fill the frame
quote::minimal
Clean editorial, serif
quote::attributed
Author-led, prominent name
quote::contrast
Image top, card bottom
quote::ticker
Bold animated ticker

Contrast

contrast::versus
Two ideas face off
contrast::split_screen
Image left, insight right
contrast::myth_reality
Myth vs reality
contrast::before_after
Before and after stacked
contrast::old_new
Old way vs new way

Question

question::rhetorical
Spacious serif question
question::poll
Poll-style question
question::challenge
Direct challenge
question::bold
Display font, high impact
question::minimal
Single question, rule below

Myth

myth::bold
Dark bg, bold correction
myth::stack
Light bg, myth then reality
myth::minimal
Editorial rule divider
myth::cinematic
Full bleed, correction card
myth::split
Left muted myth, right bold fix

Checklist

checklist::minimal
Clean arrow list, whitespace
checklist::bold
Dark bg, high contrast list
checklist::do_dont
Two columns, wrong vs right
checklist::audit
Audit-style checklist
checklist::score
Score yourself numbered list

Trend

trend::rising
Upward energy, bold accent
trend::declining
Muted warning tone
trend::watch
Neutral editorial watch format
trend::editorial
Serif thought leadership
trend::data
Dark bg, stat hero, context below

Prediction

prediction::bold_call
Strong bold prediction
prediction::cinematic
Full bleed, dramatic call
prediction::editorial
Measured editorial tone
prediction::minimal
Clean, single prediction
prediction::three_up
Three predictions in a grid

Multi-slide

framework::ticker
5-slide principles / steps post
journey::chapter
3-slide story arc or journey

Media — Image requires asset_url

Pass a public image URL in asset_url. AI generates the caption from your topic.

media::overlay
Full bleed image, caption overlaid
media::framed
Image with brand frame border
media::split
Image top, caption below
media::caption_bar
Image with brand bar caption
media::montage
Ken Burns image, shared caption

Media — Video overlay requires asset_url (.mp4)

Pass a public .mp4 URL in asset_url. The overlay renders on top of the playing video.

media::video_brand_bar
Brand bar overlay at bottom
media::video_gradient
Gradient scrim with caption
media::video_cinematic
Cinematic bars top and bottom
media::video_minimal
Brand dot and watermark only

Webhooks

Pass a callback_url in the generate request to receive a notification when the job completes — no polling needed.

Single job payload

{
  "job_id": "job_123",
  "status": "done",
  "output_url": "https://pub-xxx.r2.dev/renders/job_123.png",
  "error": null
}

HMAC signature verification

Every webhook includes signature headers so you can verify it came from Frekto:

X-Frekto-Signature: t=1783200000,v1=abc123def456...
X-Frekto-Timestamp: 1783200000

The signature is HMAC-SHA256(timestamp + "." + body, signing_secret). Verify it before trusting the payload:

// Node.js verification
const crypto = require('crypto');

function verifyFrektoWebhook(rawBody, signatureHeader, apiKeyHash) {
  const [tPart, v1Part] = signatureHeader.split(',');
  const timestamp = tPart.split('=')[1];
  const v1        = v1Part.split('=')[1];

  // Reject webhooks older than 5 minutes
  if (Math.floor(Date.now() / 1000) - parseInt(timestamp) > 300) return false;

  const expected = crypto
    .createHmac('sha256', apiKeyHash)
    .update(timestamp + '.' + rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
}

// Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-frekto-signature'];
  if (!verifyFrektoWebhook(req.body.toString(), sig, YOUR_API_KEY_HASH))
    return res.status(401).send('Invalid signature');
  const payload = JSON.parse(req.body);
  res.sendStatus(200);
});
HeaderValue
X-Frekto-Signaturet=<unix_seconds>,v1=<hmac_sha256_hex>
X-Frekto-TimestampUnix timestamp (seconds) — reject if > 5 min old

Code Examples

JavaScript
Python
cURL
async function generatePost(topic, apiKey, options = {}) {
  // Submit job
  const res = await fetch('https://api.frekto.ai/generate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      topic,
      format: options.format ?? '4:5',
      output_format: options.output_format ?? 'png',
      style: options.style ?? null,
    })
  });

  const data = await res.json();
  if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);

  // Poll until done (every 3s, timeout after 3 min)
  const deadline = Date.now() + 180_000;
  while (Date.now() < deadline) {
    await new Promise(r => setTimeout(r, 3000));
    const job = await fetch(`https://api.frekto.ai/jobs/${data.job_id}`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json());

    if (job.status === 'done')   return job.output_url;
    if (job.status === 'failed') throw new Error(job.error ?? 'Render failed');
  }
  throw new Error('Timed out waiting for render');
}

// Usage
try {
  const url = await generatePost(
    'AI is transforming healthcare',
    'frekto_live_YOUR_KEY',
    { format: '4:5', output_format: 'mp4' }
  );
  console.log('Generated:', url);
} catch (err) {
  console.error('Error:', err.message);
}
import requests, time

def generate_post(topic, api_key, format='4:5', output_format='png', style=None):
    headers = {'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}

    # Submit job
    res = requests.post(
        'https://api.frekto.ai/generate',
        headers=headers,
        json={'topic': topic, 'format': format, 'output_format': output_format, 'style': style}
    )
    data = res.json()
    if not res.ok:
        raise Exception(data.get('error', f'HTTP {res.status_code}'))

    job_id = data['job_id']

    # Poll until done (timeout after 3 min)
    deadline = time.time() + 180
    while time.time() < deadline:
        time.sleep(3)
        job = requests.get(f'https://api.frekto.ai/jobs/{job_id}', headers=headers).json()
        if job['status'] == 'done':   return job['output_url']
        if job['status'] == 'failed': raise Exception(job.get('error', 'Render failed'))
        print('Status:', job['status'])

    raise Exception('Timed out waiting for render')

# Usage
try:
    url = generate_post(
        'AI is transforming healthcare',
        'frekto_live_YOUR_KEY',
        format='4:5', output_format='mp4'
    )
    print('Generated:', url)
except Exception as e:
    print('Error:', e)
# Submit job
curl -X POST https://api.frekto.ai/generate \
  -H "Authorization: Bearer frekto_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "AI is transforming healthcare",
    "format": "4:5",
    "output_format": "png"
  }'

# Poll status
curl https://api.frekto.ai/jobs/job_123 \
  -H "Authorization: Bearer frekto_live_YOUR_KEY"

Series Example

Generate a full content series with one call — AI plans the topics, Railway renders each post, and the worker auto-schedules them.

JavaScript
Python
cURL
async function generateSeries(instruction, apiKey, options = {}) {
  // Start the series
  const res = await fetch('https://api.frekto.ai/generate/series', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      instruction,
      count:         options.count         ?? 3,
      platform:      options.platform      ?? 'linkedin',
      cadence:       options.cadence       ?? 'weekdays',
      output_format: options.output_format ?? 'mp4',
      schedule:      options.startDate ? { start_date: options.startDate } : undefined,
      callback_url:  options.callbackUrl   ?? undefined,
    })
  });
  const { series_id } = await res.json();
  console.log('Series started:', series_id);

  // Poll until scheduled
  while (true) {
    await new Promise(r => setTimeout(r, 5000));
    const status = await fetch(`https://api.frekto.ai/series/${series_id}`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json());

    console.log(`${status.completed}/${status.total} complete — ${status.status}`);
    status.posts.forEach(p => {
      if (p.url) console.log(`  Post ${p.post_number}: ${p.url}`);
    });

    if (status.status === 'scheduled') return status.posts;
    if (status.status === 'failed')    throw new Error('Series failed');
  }
}

// Usage
const posts = await generateSeries(
  '5 posts on building a personal brand as a developer',
  'frekto_live_YOUR_KEY',
  { count: 3, platform: 'linkedin', startDate: '2026-07-08' }
);
console.log('All scheduled:', posts.map(p => p.url));
import requests, time

def generate_series(instruction, api_key, count=3, platform='linkedin', start_date=None):
    headers = {'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}

    # Start the series
    body = {'instruction': instruction, 'count': count, 'platform': platform, 'output_format': 'mp4'}
    if start_date:
        body['schedule'] = {'start_date': start_date}

    res = requests.post('https://api.frekto.ai/generate/series', headers=headers, json=body)
    series_id = res.json()['series_id']
    print(f'Series started: {series_id}')

    # Poll until scheduled
    while True:
        time.sleep(5)
        status = requests.get(f'https://api.frekto.ai/series/{series_id}', headers=headers).json()
        print(f"{status['completed']}/{status['total']} complete — {status['status']}")
        for p in status['posts']:
            if p.get('url'):
                print(f"  Post {p['post_number']}: {p['url']}")
        if status['status'] == 'scheduled': return status['posts']
        if status['status'] == 'failed':    raise Exception('Series failed')

# Usage
posts = generate_series(
    '5 posts on building a personal brand as a developer',
    'frekto_live_YOUR_KEY',
    count=3, start_date='2026-07-08'
)
print('All scheduled:', [p['url'] for p in posts])
# Start series
curl -X POST https://api.frekto.ai/generate/series \
  -H "Authorization: Bearer frekto_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instruction": "5 posts on building a personal brand as a developer",
    "count": 3,
    "platform": "linkedin",
    "output_format": "mp4",
    "schedule": { "start_date": "2026-07-08" }
  }'

# Poll status (replace SERIES_ID)
curl https://api.frekto.ai/series/SERIES_ID \
  -H "Authorization: Bearer frekto_live_YOUR_KEY"

MCP Integration

Use Frekto directly from Claude Desktop or any MCP-compatible AI assistant. Once connected, Claude can generate posts and series by just being asked.

Claude Desktop config
{
  "mcpServers": {
    "frekto": {
      "url": "https://mcp.frekto.ai/mcp",
      "headers": {
        "Authorization": "Bearer frekto_live_YOUR_KEY"
      }
    }
  }
}

Available tools

ToolWhat it does
generate_post Generate a single post from a topic. Waits for completion and returns the URL. Accepts topic, style, format, output_format, asset_url, callback_url.
get_job_status Check the status of a single render job by job_id. Returns status and output_url when done.
generate_series Generate a full content series from one instruction. AI plans N posts, renders in parallel, auto-schedules. Returns series_id immediately — poll with get_series_status.
get_series_status Poll a series by series_id. Shows per-post progress. When status is "scheduled" all posts are rendered and in the publish queue.
list_styles List all available template styles, optionally filtered by category (revelation, stat, quote, etc.).
create_api_key Create a new Frekto API key for programmatic access.
list_api_keys List all API keys for the current account.

Example prompts

"Generate a post about AI trends in healthcare as an MP4"

"Create a series of 3 LinkedIn posts about building a personal brand as a developer, starting next Monday"

"What template styles are available for stat posts?"