The Prolifik REST API works on the same account as the dashboard, with the same brands, plan limits and ownership checks. It covers the AI Clipper, content jobs and publishing, AI Studio, AI influencers and cloning. If you work inside an AI assistant rather than your own code, the Prolifik MCP server offers these operations as tools.
Updated 13 October 2026
| Setting | Value |
|---|---|
| Base URL | https://app.getprolifik.com/api/v1 |
| Format | JSON in and out. Send Content-Type: application/json with every body. |
| Authentication | An API key as a bearer token: Authorization: Bearer plk_live_... |
| Who can use it | Accounts on a paid plan: Lite, Starter, Pro or Enterprise. AI Studio, influencers and cloning need Starter or higher. |
| Rate limit | 120 requests per minute per key, with tighter limits on calls that start paid work |
| Endpoints | 61, all documented below |
| OpenAPI document | app.getprolifik.com/api/v1/openapi.json (OpenAPI 3.1) |
Your first request
- In the Prolifik dashboard, open Settings > Developer API and create a key with the scopes you need. Copy it: it is shown once.
- Keep it in an environment variable, never in frontend code or a public repository:
export PROLIFIK_API_KEY=plk_live_... - List your brands:
curl https://app.getprolifik.com/api/v1/brands \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
A 200 with a brands array means the key works (this call needs the content:read scope). From there, a typical integration does one of three things: clips a long video (create a clip job, poll it, then post a clip), produces branded posts (create a content job, poll it, then publish a piece), or reads results into another tool.
Authentication
Every request carries an API key in the Authorization header:
Authorization: Bearer plk_live_...
- Creating keys. Open Settings > Developer API, give the key a name, tick its scopes, and pick an expiry of 30 days, 90 days or 1 year. Prolifik shows the key once and stores only a hash of it. An account can have up to 10 active keys.
- Paid plans only. API keys come with Lite, Starter, Pro and Enterprise. Accounts on the 7-day free trial cannot create keys; during the trial, connect an AI assistant through the MCP server instead. If the account loses its paid plan, its keys can still read, cancel and delete, and every call that starts work, edits or publishes returns 402
subscription_requireduntil a plan is active again. Cloning is stricter: every cloning endpoint, reads included, needs a plan with AI Studio (Starter or higher). - Revoking. Revoke a key in the same settings panel. A revoked or expired key gets 401 on its next request.
- OAuth. The REST API takes API keys only. OAuth sign-in is for MCP clients at
https://app.getprolifik.com/mcp.
Scopes
A key can call only the endpoints its scopes allow, and anything else returns 403 insufficient_scope. Write scopes do not include read scopes, so a key that creates jobs and then reads them needs both. Give each integration the fewest scopes it needs, and add publish:write only to keys that should post publicly.
Requests and responses
- Bodies are JSON objects of at most 64 KB. Larger bodies get 413; files go through the upload-link endpoints instead.
- IDs are UUIDs, except clip jobs and clips, which use short string IDs such as
3f9a1c2b7d4eand3f9a1c2b7d4e_clip_000. - Times come back as ISO 8601 in UTC (
2026-10-14T13:00:00.000Z). Send them with aZor an offset. - Field names are camelCase, except the Clipper job and clip objects, which use snake_case (
job_id,file_url). - Media links in responses (videos, slides, thumbnails, generation outputs) are signed and expire, most within the hour. Read the resource again for a fresh link instead of storing one.
- Response headers. Every response has
x-request-id. Authenticated responses also carryx-ratelimit-limit,x-ratelimit-remainingandx-ratelimit-reset(Unix seconds). Responses are never cached (cache-control: no-store).
Errors
Every error has the same shape: a machine-readable code, a message you can show a person, and the requestId that is also in the x-request-id header.
{
"error": {
"code": "insufficient_scope",
"message": "This API key requires the publish:write scope.",
"requestId": "0d5c3b8e-6f1a-4c2d-9b7e-3a5f8c1d2e4b"
}
}
A 402 can add fields: upgradeUrl on subscription_required, and needed, balance and upgradeUrl on insufficient_credits.
{
"error": {
"code": "insufficient_credits",
"message": "This request needs 68 AI credits and the account has 40.",
"requestId": "7c1e9a3b-2d4f-4e6a-8b1c-5d7f9a2c4e6b",
"needed": 68,
"balance": 40,
"upgradeUrl": "https://app.getprolifik.com/pricing"
}
}
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter or body field is missing, has the wrong type or is out of range, or the body is not a JSON object. The message names the field. |
| 401 | unauthorized | No key, a key that does not start with plk_live_, or a key that is revoked or expired. |
| 402 | subscription_required | The account has no paid plan, or the plan does not include the feature (AI Studio, influencers and cloning need Starter or higher). upgradeUrl says where to fix it. |
| 402 | insufficient_credits | The request costs more AI credits than the account has. needed and balance give the numbers. |
| 402 | quota_exceeded | A limit of the plan was reached: clip minutes, the monthly render budget, monthly videos, the brand count, or a brand or account the plan has locked. A subscription with an unpaid invoice also gets this code. |
| 403 | insufficient_scope | The key works but lacks the scope this endpoint needs. The WWW-Authenticate header names the scope. |
| 403 | forbidden | The account may not use this part of the product right now, for example Clipper calls from an account that has never had a plan or trial. |
| 404 | not_found | Nothing with that ID exists on your account. |
| 409 | conflict | The resource is in the wrong state (cancelling a finished job, editing a post that already went out), or the Idempotency-Key was used with different data or is still being processed. The message says which. |
| 409 | in_progress | A distribute for this cloning job is already running. |
| 413 | payload_too_large | The JSON body is over 64 KB, or an uploaded clip source is over 8 GB. |
| 429 | rate_limit_exceeded | Too many requests. Wait the number of seconds in the Retry-After header. |
| 500 | internal_error | Something failed on Prolifik's side. Before you retry a call that starts work or posts, list that resource to check whether the first attempt got through. |
| 502 | upstream_error | The social posting provider refused to change or cancel a queued post. Nothing changed on Prolifik's side. |
| 503 | service_unavailable | A background worker could not be reached, so the work was not queued and any credits were refunded. Retry shortly. |
Branch on code, not on the message text, which can change.
Rate limits
Limits use fixed windows. Over a limit, the request fails with 429 rate_limit_exceeded and a Retry-After header in seconds. The x-ratelimit-* headers describe the 120-per-minute window, even when a tighter per-operation limit answered, so wait on Retry-After.
| Calls | Limit | Counted |
|---|---|---|
| Every request | 120 per minute | Per API key |
POST /clipper/jobs | 10 per minute and 30 per hour | Minute window per key, hour window shared by every key on the account |
POST /brands | 5 per minute | Per key |
POST /content/sources and re-analyze | 10 per minute, shared | Per key |
POST /content/jobs | 5 per minute | Per key |
POST /content/uploads | 20 per minute | Per key |
POST /studio/generations | 5 per minute | Per key |
POST /studio/uploads | 20 per minute | Per key |
| Influencer create, regenerate and delete | 5 per minute, shared | Per key |
| Cloning create, add variations, translate, retry and distribute | 10 per 10 minutes, each | Per key |
Pagination
Lists return newest first and take two query parameters:
limit: 1 to 100, default 50. Values out of range are clamped; a value that is not a number returns 400.before: thenextCursorof the page you just read.
Every list answers with hasMore and nextCursor. While hasMore is true, ask again with before set to nextCursor exactly as you got it. On the last page hasMore is false and nextCursor is null. Items are ordered by creation time and then by ID, so paging this way never skips or repeats an item, even when a whole batch was created in the same instant.
curl "https://app.getprolifik.com/api/v1/content/pieces?limit=100&before=MjAyNi0xMC0xM1QxMDoxNTowMC4xMDQ1NjdafDFlM2E1YzdiLTlkMmYtNGI2ZS04YTFjLTNlNWY3YjlkMmE0Yw" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
A cursor is opaque. Pass it back unchanged and don't parse or build one: what it holds can change. before also still takes an ISO 8601 timestamp and then returns items created strictly before it. That works, but createdAt is rounded to the millisecond, so a timestamp taken from it can skip items created in the same millisecond as the last one you saw. A before that is neither a cursor nor a timestamp returns 400.
A few lists are short enough to come back whole: brands, influencers, publish destinations, and clip publish records (the latest 200).
Idempotency
Calls that start paid work or post publicly take an Idempotency-Key header, so a retry after a timeout cannot queue a second job, charge twice or post twice.
- The key is 8 to 128 letters, digits, dots, colons, underscores or hyphens. A UUID works. Use a new key for each new action.
- Prolifik keeps the key for 24 hours, per API key and per endpoint. Sending the same key with the same body returns the first response again, with the header
x-idempotent-replayed: true. - The same key with a different body returns 409. So does a retry that arrives while the first request is still running; it carries
Retry-After: 2. - A request that fails normally releases its key, so a retry with the same key runs the request again instead of replaying the error. After a 500, check the resource before you retry (see Errors). If the key still gets 409 after the first request has answered, the first attempt may have gone through: check the resource before you try a new key.
| Idempotency-Key | Endpoints |
|---|---|
| Required | POST /brands, POST /content/sources, POST /content/jobs, PATCH /content/pieces/{pieceId}, POST /content/pieces/{pieceId}/regenerate, POST /content/pieces/{pieceId}/retry, POST /content/pieces/{pieceId}/publish, POST /studio/generations, POST /influencers, POST /influencers/{influencerId}/regenerate, POST /cloning/jobs, POST /cloning/jobs/{jobId}/localize, POST /cloning/jobs/{jobId}/distribute |
| Optional, recommended | POST /clipper/jobs, POST /clipper/clips/{clipId}/publish, POST /cloning/jobs/{jobId}/variations, POST /cloning/jobs/{jobId}/retry. Without a key, a repeated clip-job request queues and bills a second job, a repeated clip post returns the existing post, and a repeated cloning top-up or retry gets 409 while the first one runs. |
Long-running work
Clipping, rendering and generation take from seconds to several minutes, so the create call returns straight away (usually with 202) and you poll the resource until it settles. The API has no webhooks today. Polling every 10 to 30 seconds is plenty, and each poll counts toward the 120 requests a minute.
| Work | Started by | Poll | Done when |
|---|---|---|---|
| Clip job | POST /clipper/jobs | GET /clipper/jobs/{jobId}/status | completed, failed or cancelled |
| Brand analysis | POST /brands | GET /brands/{brandId} | enrichmentStatus is complete or failed |
| Content source | POST /content/sources | GET /content/sources/{sourceId} | ready or failed |
| Content job | POST /content/jobs | GET /content/jobs/{jobId} | ready_for_review, completed, partially_failed or canceled |
| Piece edit or retry | PATCH or retry a piece | GET /content/pieces/{pieceId} | ready or failed |
| AI Studio generation | POST /studio/generations | GET /studio/generations/{generationId} | complete or failed |
| AI influencer | POST /influencers | GET /influencers/{influencerId} | base_ready or failed |
| Cloning job | POST /cloning/jobs | GET /cloning/jobs/{jobId} | ready or failed |
All endpoints
61 endpoints in ten areas. Every path below is relative to https://app.getprolifik.com/api/v1. In the examples, $PROLIFIK_API_KEY holds your key, and responses are trimmed where they are long: ... marks fields left out.
| Endpoint | What it does | Scope |
|---|---|---|
GET /clipper/jobs | List clip jobs | clipper:read |
POST /clipper/jobs | Create a clip job | clipper:write |
GET /clipper/minutes | Get the clip-minute balance | clipper:read |
GET /clipper/jobs/{jobId} | Get a clip job and its clips | clipper:read |
DELETE /clipper/jobs/{jobId} | Cancel a clip job | clipper:write |
GET /clipper/jobs/{jobId}/status | Poll clip job status | clipper:read |
POST /clipper/upload-url | Create a source upload URL | clipper:write |
GET /clipper/publish-destinations | List clip publish destinations | clipper:read |
POST /clipper/clips/{clipId}/publish | Publish or schedule a clip | clipper:write |
GET /clipper/publishes | List clip publish records | clipper:read |
POST /clipper/publishes/{publishId}/retry | Retry a failed clip post | clipper:write |
GET /brands | List brands | content:read |
POST /brands | Create a brand | content:write |
GET /brands/{brandId} | Get a brand | content:read |
PATCH /brands/{brandId} | Update a brand | content:write |
GET /content/sources | List content sources | content:read |
POST /content/sources | Add a content source | content:write |
GET /content/sources/{sourceId} | Get a content source | content:read |
PATCH /content/sources/{sourceId} | Rename a source or edit its notes | content:write |
DELETE /content/sources/{sourceId} | Delete a content source | content:write |
POST /content/sources/{sourceId}/reanalyze | Re-analyze a content source | content:write |
GET /content/jobs | List content jobs | content:read |
POST /content/jobs | Create a content job | content:write |
GET /content/jobs/{jobId} | Get a content job | content:read |
DELETE /content/jobs/{jobId} | Cancel a content job | content:write |
GET /content/pieces | List pieces | content:read |
GET /content/pieces/{pieceId} | Get a piece | content:read |
PATCH /content/pieces/{pieceId} | Edit a piece and render it again | content:write |
DELETE /content/pieces/{pieceId} | Delete a piece | content:write |
POST /content/pieces/{pieceId}/regenerate | Regenerate a failed piece | content:write |
POST /content/pieces/{pieceId}/retry | Retry a failed render | content:write |
GET /publish-destinations | List publish destinations | content:read |
POST /content/pieces/{pieceId}/publish | Publish or schedule a piece | publish:write |
GET /content/publishes | List content posts | content:read |
GET /content/publishes/{publishId} | Get a content post | content:read |
PATCH /content/publishes/{publishId} | Edit a queued post's caption | publish:write |
DELETE /content/publishes/{publishId} | Cancel a queued post | publish:write |
GET /usage | Get plan usage | content:read |
GET /content/media | List the media library | content:read |
POST /content/uploads | Create a demo upload URL | content:write |
GET /studio/models | List AI Studio models and prices | studio:read |
GET /studio/credits | Get the AI credit balance | studio:read |
POST /studio/uploads | Create a reference image upload URL | studio:write |
GET /studio/generations | List AI Studio generations | studio:read |
POST /studio/generations | Queue an AI Studio generation | studio:write |
GET /studio/generations/{generationId} | Get a generation | studio:read |
GET /influencers | List AI influencers | studio:read |
POST /influencers | Create an AI influencer | studio:write |
GET /influencers/{influencerId} | Get an AI influencer | studio:read |
DELETE /influencers/{influencerId} | Delete an AI influencer | studio:write |
POST /influencers/{influencerId}/regenerate | Regenerate an influencer portrait | studio:write |
POST /cloning/estimate | Quote a clone | studio:read |
GET /cloning/jobs | List cloning jobs | studio:read |
POST /cloning/jobs | Clone a TikTok or Instagram post | studio:write |
GET /cloning/jobs/{jobId} | Get a cloning job and its variations | studio:read |
DELETE /cloning/jobs/{jobId} | Delete a cloning job | studio:write |
POST /cloning/jobs/{jobId}/variations | Add variations to a job | studio:write |
POST /cloning/jobs/{jobId}/localize | Translate variations | studio:write |
POST /cloning/jobs/{jobId}/retry | Retry a failed cloning job | studio:write |
POST /cloning/jobs/{jobId}/distribute | Schedule variations across accounts | publish:write |
POST /cloning/pieces/{pieceId}/library | Save a variation to the Library | studio:write |
Clipper
Turn a long YouTube, Twitch or Kick video, or your own upload, into vertical clips with captions, then post them. Clip jobs use clip minutes: 60 a month on Lite, 360 on Starter, 1,400 on Pro, unmetered on Enterprise.
List clip jobs
GET/clipper/jobs
Your clip jobs, newest first. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/clipper/jobs?limit=20" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"jobs": [
{
"job_id": "3f9a1c2b7d4e",
"status": "completed",
"source_title": "How we got our first 1,000 users",
"source_channel": "Acme Podcast",
"source_url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
"num_clips": 3,
"created_at": "2026-10-13T09:12:44.512Z",
"completed_at": "2026-10-13T09:21:03.118Z",
"error": null,
"thumbs": ["https://...", "https://...", "https://..."],
"progress": null
}
],
"hasMore": false,
"nextCursor": null
}
In this list, num_clips is the number of clips the job has produced so far. progress is null once a job is completed, failed or cancelled, and otherwise holds step, total_steps, current_step_name and percent. For an uploaded file, source_url is "uploaded".
Create a clip job
POST/clipper/jobs
Queues a video for clipping and returns at once. Send one of url, urls or sourceStoragePath. The job uses clip minutes for the length of the source video, so check the balance first: a job needs at least 5 minutes left.
Accepted links: one YouTube video (watch, youtu.be, Shorts, live or embed links), a Twitch VOD or clip, or a Kick VOD or clip. Channel pages, playlists and live streams on Twitch and Kick are refused with 400.
Settings are forgiving. Numbers out of range are clamped, an inverted minDuration/maxDuration pair is swapped, and an unknown value for category, layout, captionStyle or aspectRatio falls back to its default. Unknown or malformed keys inside options are dropped.
Body
| Field | Type | Description |
|---|---|---|
url | string see description | A link to one video. One of url, urls or sourceStoragePath is required. |
urls | array of strings see description | Up to 10 links, queued as a batch with one job per link. Takes priority over url. If any link is not supported, the whole batch is refused. |
sourceStoragePath | string see description | The path from Create a source upload URL, after you have uploaded the file. |
sourceTitle | string optional | Title for an uploaded file, up to 300 characters. Ignored for links. |
sourceChannel | string optional | Channel name for an uploaded file, up to 300 characters. Default Upload. |
numClips | integer optional | 1 to 20. Default 3. |
minDuration | integer optional | Shortest clip in seconds, 5 to 600. Default 30. |
maxDuration | integer optional | Longest clip in seconds, 5 to 600. Default 90. |
category | string optional | generic (default), meme, sports or movies. |
layout | string optional | blur (default) keeps the whole frame with a blurred fill above and below. crop zooms into the middle. smart follows the face. speaker and smart_crop are also accepted. |
captionStyle | string optional | classic (default), karaoke (words light up as spoken), bold, minimal, neon or none. |
aspectRatio | string optional | 9:16 (default), 1:1, 16:9 or 4:5. |
userPrompt | string optional | Extra instructions for the clip finder, up to 2,000 characters. |
options | object optional | Render and selection options. See the table below. |
Options
| Field | Type | Description |
|---|---|---|
language | string optional | Spoken language of the source as a language tag (en, es, pt-BR), or auto to detect it. |
format | string optional | What kind of video it is, which sets the rules for picking moments: generic, podcast (interviews and talking heads), reaction (a streamer reacting to a game or video), casino (slots or casino play), broadcast (sports with no reaction cam), meme or cinematic. |
mode | string optional | captions burns captions on the whole video (up to 15 minutes) as one clip instead of finding moments. numClips becomes 1, and captionStyle cannot be none. |
speaker_tracking | boolean optional | Reframe to follow the active speaker on videos with several people. |
auto_zoom | boolean optional | Punch-in zooms on emphasis. |
emoji_captions | boolean optional | Add emoji to caption keywords. |
remove_fillers | boolean optional | Cut filler words and long silences. |
visual_selection | boolean optional | Also look for moments with no speech, using vision analysis. |
multicam | boolean optional | Alternate wide and tight framing like a second camera. |
sfx_hits | boolean optional | Whoosh and a small shake on punch-in moments. |
cutaways | boolean optional | Cut away to b-roll picked from the source video itself. |
include_logo | boolean optional | Burn the active brand's logo into the clips. Quietly turned off when the brand has no logo. |
brand_kit | object optional | Caption styling: primary and secondary as #rgb or #rrggbb, and font (a font family name, up to 64 characters). |
quality.brand_safety | string optional | standard or gambling. Gambling leaves out rival brands, sexual content, addiction, anything involving minors, and complaints that a site does not pay. |
quality.client_rules | string optional | Your own rules for picking clips, up to 2,000 characters. The moment picker follows them. |
quality.banned_words | array of strings optional | Up to 50 words or phrases (60 characters each). A clip in which one is spoken is dropped. |
quality.vocabulary | array of strings optional | Up to 100 correct spellings of names, brands and terms the captions must get right. |
quality.dictionary | object optional | Up to 100 caption fixes, mapping what the transcript tends to hear to the right spelling, for example {"prolific": "Prolifik"}. |
quality.key_moments | string optional | auto (default) gives goals, red cards, big wins and similar moments their own clip. off drops that guarantee. |
Request
curl -X POST https://app.getprolifik.com/api/v1/clipper/jobs \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clip-2026-10-13-ep42" \
-d '{
"url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
"numClips": 5,
"captionStyle": "karaoke",
"options": { "format": "podcast", "quality": { "vocabulary": ["Prolifik"] } }
}'
Response 202
{
"job_id": "3f9a1c2b7d4e",
"status": "queued",
"created_at": "2026-10-13T09:12:44.512Z"
}
A batch (urls) returns { "batch_id": "...", "jobs": [{ "job_id", "status", "created_at" }] }. Each link becomes its own job and is billed separately, duplicates included.
Errors: 400 for an unsupported link or a missing source; 402 quota_exceeded when fewer than 5 clip minutes are left; 402 subscription_required without a paid plan; 413 when an uploaded source is over 8 GB; 429 past 10 jobs a minute per key or 30 an hour per account.
Get the clip-minute balance
GET/clipper/minutes
Clip minutes left in the current billing period, the plan's allowance, and whether a new job would be accepted. Plans include 60 minutes a month on Lite, 360 on Starter and 1,400 on Pro. Enterprise is unmetered and reports -1 with unlimited: true.
Request
curl https://app.getprolifik.com/api/v1/clipper/minutes \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"clipMinutes": {
"balance": 318.5,
"allowance": 360,
"unlimited": false,
"plan": "starter",
"minimumJobMinutes": 5,
"canQueueJob": true
}
}
canQueueJob only compares the balance with minimumJobMinutes. A job can still be refused for other reasons, such as an unpaid plan.
Get a clip job and its clips
GET/clipper/jobs/{jobId}
One job, the settings it ran with, and every clip it produced, in order. Clip media links can expire, so read the job again for fresh links rather than storing them.
Path
| Field | Type | Description |
|---|---|---|
jobId | string required | The job_id from create or list. |
Request
curl https://app.getprolifik.com/api/v1/clipper/jobs/3f9a1c2b7d4e \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"job_id": "3f9a1c2b7d4e",
"status": "completed",
"source": {
"url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
"title": "How we got our first 1,000 users",
"channel": "Acme Podcast",
"duration": 2841
},
"progress": { "step": 4, "total_steps": 4, "current_step_name": "completed", "elapsed_seconds": 499 },
"num_clips": 3,
"min_duration": 30,
"max_duration": 90,
"category": "generic",
"layout": "blur",
"caption_style": "karaoke",
"aspect_ratio": "9:16",
"user_prompt": "",
"auto_publish": false,
"options": { "format": "podcast" },
"clips": [
{
"clip_id": "3f9a1c2b7d4e_clip_000",
"file_url": "https://...",
"thumbnail_url": "https://...",
"start_time": 412.3,
"end_time": 468.9,
"duration": 56.6,
"title": "The cold email that got us 300 users",
"description": "The founder explains the one-line email ...",
"hook": "We sent one email and got 300 signups.",
"hashtags": ["#startup", "#growth"],
"virality_score": 82,
"status": "completed",
"error": null,
"qc": {
"status": "passed",
"checks": [{ "id": "start_sentence", "ok": true, "detail": null }]
},
"key_moment": null
}
],
"created_at": "2026-10-13T09:12:44.512Z",
"completed_at": "2026-10-13T09:21:03.118Z",
"processing_time_seconds": 499,
"error": null
}
Here num_clips is the number you asked for. Each clip also carries long_description, overlay_text, score_breakdown, tags, rating, overlays and logo fields, trimmed above. qc is the post-render check (passed, warning or failed, with one entry per check); it is null on older clips, and a failed clip is never posted automatically. key_moment is { kind, label, t } for a goal, red card, penalty, big win, bonus or peak, or null.
A missing job, or one owned by another account, returns 404.
Cancel a clip job
DELETE/clipper/jobs/{jobId}
Stops a job that is still queued or running.
Path
| Field | Type | Description |
|---|---|---|
jobId | string required | The job to cancel. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/clipper/jobs/3f9a1c2b7d4e \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true }
Returns 409 when the job is no longer running. The same 409 comes back for an ID that does not exist.
Poll clip job status
GET/clipper/jobs/{jobId}/status
A light read for polling a running job. Status moves through queued, downloading, transcribing, selecting and producing to completed, failed or cancelled.
Path
| Field | Type | Description |
|---|---|---|
jobId | string required | The job to check. |
Request
curl https://app.getprolifik.com/api/v1/clipper/jobs/3f9a1c2b7d4e/status \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"status": "selecting",
"progress": {
"step": 3,
"total_steps": 4,
"current_step_name": "selecting",
"elapsed_seconds": 214,
"percent": 40
},
"clipCount": 0
}
progress.message and progress.percent appear only when the worker reports them. clipCount counts the clips that exist so far.
Create a source upload URL
POST/clipper/upload-url
To clip a file instead of a link: ask for an upload URL, PUT the file's bytes to uploadUrl within 15 minutes, then create the job with sourceStoragePath set to the returned path. Accepted extensions are mp4, mov, webm, m4v, mkv and avi. A source must be under 8 GB, which is checked when you create the job.
Body
| Field | Type | Description |
|---|---|---|
fileName | string optional | Default source.mp4. Characters other than letters, digits, dot, underscore and hyphen are replaced with _. |
jobId | string optional | Reuse an ID from an earlier upload to put the file in the same folder. Omit it to get a new one. |
Request
curl -X POST https://app.getprolifik.com/api/v1/clipper/upload-url \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "fileName": "episode-42.mp4" }'
# then upload the file
curl -X PUT --upload-file episode-42.mp4 "<uploadUrl>"
Response 201
{
"jobId": "9b2e4f7a1c3d",
"path": "<your user id>/9b2e4f7a1c3d/episode-42.mp4",
"uploadUrl": "https://..."
}
The created clip job takes the upload's jobId as its job_id.
List clip publish destinations
GET/clipper/publish-destinations
The connected social accounts a clip can be posted to. Without brandId it lists the account's active brand. A brand you do not own, or one your plan has locked, returns an empty list.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID optional | List another brand's accounts. |
Request
curl "https://app.getprolifik.com/api/v1/clipper/publish-destinations?brandId=6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"destinations": [
{ "id": "8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "platform": "tiktok", "accountName": "acmehq" },
{ "id": "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b", "platform": "instagram", "accountName": "acme" }
]
}
Use an id as connectionId when you post a clip.
Publish or schedule a clip
POST/clipper/clips/{clipId}/publish
Creates a real post on a connected social account, now or at scheduledFor. The caption is built from the clip's title, description and hashtags. The account must be an active destination on one of your plan's brands.
A clip goes to the same account once: while a post of this clip to this account is scheduled, posting or already out, calling this again returns that post instead of creating another. Each account also has a daily post limit (20 on TikTok, 6 on YouTube, 100 elsewhere), counted per UTC day.
Path
| Field | Type | Description |
|---|---|---|
clipId | string required | The clip_id from the job. |
Body
| Field | Type | Description |
|---|---|---|
connectionId | UUID required | A destination id from List clip publish destinations. |
scheduledFor | ISO 8601 timestamp optional | When to post, up to a year ahead. Omit to post now. |
Request
curl -X POST https://app.getprolifik.com/api/v1/clipper/clips/3f9a1c2b7d4e_clip_000/publish \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: post-3f9a1c2b7d4e_clip_000-tiktok" \
-d '{ "connectionId": "8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "scheduledFor": "2026-10-14T13:00:00Z" }'
Response 201
{
"id": "c2a7e9d4-1f3b-4a8e-b6d0-5e9f2a1c7b38",
"source": "clip",
"job_id": "3f9a1c2b7d4e",
"clip_id": "3f9a1c2b7d4e_clip_000",
"account_id": "8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54",
"platform": "tiktok",
"account_handle": "acmehq",
"title": "The cold email that got us 300 users",
"description": "The cold email that got us 300 users ...",
"published_at": "2026-10-13T09:30:12.004Z",
"status": "pending",
"external_post_id": null,
"error": null,
"attempts": 0,
"scheduled_for": "2026-10-14T13:00:00.000Z",
"completed_at": null
}
A post-now request comes back as publishing, a scheduled one as pending. Errors: 400 for a time in the past or more than a year out; 402 when connectionId is not an active destination; 404 for an unknown clip; 409 when the account needs reconnecting, the clip has no media yet, or the account hit its daily limit.
List clip publish records
GET/clipper/publishes
Your most recent clip posts, newest first, up to 200. This list is not paginated.
Query
| Field | Type | Description |
|---|---|---|
platform | string optional | instagram, tiktok, youtube, x or linkedin. Exact match. |
status | string optional | pending, publishing, published, failed or cancelled. Other values match nothing. |
jobId | string optional | Only posts of clips from this job. |
Request
curl "https://app.getprolifik.com/api/v1/clipper/publishes?status=failed" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"publishes": [
{
"id": "c2a7e9d4-1f3b-4a8e-b6d0-5e9f2a1c7b38",
"source": "clip",
"job_id": "3f9a1c2b7d4e",
"clip_id": "3f9a1c2b7d4e_clip_000",
"platform": "tiktok",
"account_handle": "acmehq",
"status": "failed",
"error": "Publishing failed",
"published_at": "2026-10-13T09:30:12.004Z"
}
]
}
Each record has the same fields as the publish response, trimmed above. Older records from before the current posting pipeline have source: "legacy".
Retry a failed clip post
POST/clipper/publishes/{publishId}/retry
Sends a failed clip post again. If the platform reports that the earlier attempt did go out, Prolifik keeps that post and does not post the clip twice.
Path
| Field | Type | Description |
|---|---|---|
publishId | string required | The id of a failed publish record. |
Request
curl -X POST https://app.getprolifik.com/api/v1/clipper/publishes/c2a7e9d4-1f3b-4a8e-b6d0-5e9f2a1c7b38/retry \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true }
Returns 409 when the post did not fail, already went out, or the account needs reconnecting, and 404 for an unknown ID.
Brands
A brand holds the voice, audience, colors, logo and posting schedule every piece is written and styled with. Lite covers 1 brand, Starter 3, Pro and Enterprise any number; brands past the limit stay readable but are locked for new work.
List brands
GET/brands
Every brand in the workspace, oldest first, with isActive showing which ones your plan covers (Lite covers 1 brand, Starter 3, Pro and Enterprise all). activeBrandId is the brand selected in the dashboard.
Request
curl https://app.getprolifik.com/api/v1/brands \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"brands": [
{
"id": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"name": "Acme",
"websiteUrl": "https://acme.com",
"voice": "Plain, confident, a little dry. Short sentences.",
"primaryColors": ["#0B1220", "#2563EB"],
"logoUrl": null,
"description": "Acme makes invoicing software for freelancers.",
"targetAudience": "Freelance designers and developers who hate chasing payments.",
"enrichmentStatus": "complete",
"postingSchedule": { "days": [1, 3, 5], "times": ["09:00", "17:30"], "timezone": "Europe/Vilnius" },
"isActive": true,
"createdAt": "2026-09-02T08:14:51.220Z"
}
],
"activeBrandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f"
}
enrichmentStatus is pending while Prolifik is still reading the website, then complete or failed.
Create a brand
POST/brands
Creates a brand from its website. Prolifik reads the page for a name, colors and a description straight away, then works out the voice and audience in the background (enrichmentStatus goes from pending to complete). Read the brand again to see the result.
If you already have a brand on the same hostname, that brand comes back with status 200 and deduped: true instead of a new one.
Body
| Field | Type | Description |
|---|---|---|
url | string required | The brand's website, with http:// or https://. Up to 2,048 characters. |
Request
curl -X POST https://app.getprolifik.com/api/v1/brands \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: brand-acme-com" \
-d '{ "url": "https://acme.com" }'
Response 202
{
"brand": {
"id": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"name": "Acme",
"websiteUrl": "https://acme.com",
"voice": "",
"enrichmentStatus": "pending",
"isActive": true,
...
},
"deduped": false
}
Errors: 400 for a URL without an http(s) scheme; 402 quota_exceeded when the plan's brand limit is reached; 429 past 5 brands a minute; 503 when the background worker is unavailable (the brand is kept with enrichmentStatus: "failed", and a retry with the same key returns it).
Get a brand
GET/brands/{brandId}
One brand's voice, audience, colors, logo and posting schedule. Brands your plan has locked can still be read.
Path
| Field | Type | Description |
|---|---|---|
brandId | UUID required | The brand. |
Request
curl https://app.getprolifik.com/api/v1/brands/6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"brand": {
"id": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"name": "Acme",
"websiteUrl": "https://acme.com",
"voice": "Plain, confident, a little dry. Short sentences.",
"primaryColors": ["#0B1220", "#2563EB"],
"logoUrl": null,
"description": "Acme makes invoicing software for freelancers.",
"targetAudience": "Freelance designers and developers who hate chasing payments.",
"enrichmentStatus": "complete",
"postingSchedule": { "days": [1, 3, 5], "times": ["09:00", "17:30"], "timezone": "Europe/Vilnius" },
"isActive": true,
"createdAt": "2026-09-02T08:14:51.220Z"
}
}
Update a brand
PATCH/brands/{brandId}
Changes the fields you send and leaves the rest alone. Send at least one. A brand your plan has locked returns 402. Changes made while enrichmentStatus is still pending are overwritten when the analysis finishes, so wait for complete.
Path
| Field | Type | Description |
|---|---|---|
brandId | UUID required | The brand. |
Body
| Field | Type | Description |
|---|---|---|
name | string or null optional | Up to 200 characters. |
voice | string or null optional | How the brand talks, up to 5,000 characters. |
primaryColors | array of strings optional | Up to 10 colors. Replaces the whole list. |
logoUrl | string or null optional | Up to 2,048 characters. |
description | string optional | Up to 5,000 characters. |
targetAudience | string optional | Up to 5,000 characters. |
postingSchedule | object or null optional | days (0 to 6, Sunday is 0), times (HH:MM, 24-hour) and timezone (an IANA zone such as America/New_York). null clears it. |
Request
curl -X PATCH https://app.getprolifik.com/api/v1/brands/6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice": "Plain, confident, a little dry. Short sentences.",
"postingSchedule": { "days": [1, 3, 5], "times": ["09:00"], "timezone": "Europe/Vilnius" }
}'
Response 200
{
"brand": { "id": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f", "name": "Acme", ... }
}
Content sources
A content source is a page Prolifik has read and summarized, such as a blog post or product page, so a content job can write about it.
List content sources
GET/content/sources
Pages a brand can base new content on, newest first. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID required | The brand. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/content/sources?brandId=6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"sources": [
{
"id": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"url": "https://acme.com/blog/late-invoices",
"title": "Why invoices get paid late",
"notes": null,
"status": "ready",
"analysis": {
"sourceType": "article",
"mainTopic": "Late invoice payments",
"argument": "Most late payments come from unclear terms, not bad clients.",
"keyPoints": ["Put the due date in the subject line", "Send the reminder before the due date"],
"audienceTakeaway": "Clear terms get you paid faster.",
"tone": "practical",
"targetAudience": "Freelancers"
},
"error": null,
"lastAnalyzedAt": "2026-10-13T09:40:02.118Z",
"createdAt": "2026-10-13T09:39:20.501Z",
"updatedAt": "2026-10-13T09:40:02.118Z"
}
],
"hasMore": false,
"nextCursor": null
}
Add a content source
POST/content/sources
Queues a page (a blog post, product page, competitor page or video page) to be read and analyzed, and returns the source with status: "pending". Poll it until status is ready or failed, then pass its ID as contentSourceId when you create a content job. A brand can have up to 50 sources that are pending or ready.
Body
| Field | Type | Description |
|---|---|---|
brandId | UUID required | A brand your plan covers. |
url | string required | The page. https:// is added if you leave the scheme off. |
title | string optional | Up to 300 characters. Defaults to the hostname. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/sources \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: src-late-invoices" \
-d '{ "brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f", "url": "https://acme.com/blog/late-invoices" }'
Response 202
{
"source": { "id": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d", "status": "pending", "analysis": null, ... }
}
Errors: 402 when the brand is locked by your plan; 404 for an unknown brand; 409 at 50 sources; 429 past 10 a minute (shared with re-analyze); 503 when the worker is unavailable. A page that cannot be fetched shows up later as status: "failed", not as an error here.
Get a content source
GET/content/sources/{sourceId}
One source with its analysis status. When status is failed, error reads "Content source analysis failed."
Path
| Field | Type | Description |
|---|---|---|
sourceId | UUID required | The source. |
Request
curl https://app.getprolifik.com/api/v1/content/sources/a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"source": {
"id": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"url": "https://acme.com/blog/late-invoices",
"title": "Why invoices get paid late",
"notes": null,
"status": "ready",
"analysis": {
"sourceType": "article",
"mainTopic": "Late invoice payments",
"argument": "Most late payments come from unclear terms, not bad clients.",
"keyPoints": ["Put the due date in the subject line", "Send the reminder before the due date"],
"audienceTakeaway": "Clear terms get you paid faster.",
"tone": "practical",
"targetAudience": "Freelancers"
},
"error": null,
"lastAnalyzedAt": "2026-10-13T09:40:02.118Z",
"createdAt": "2026-10-13T09:39:20.501Z",
"updatedAt": "2026-10-13T09:40:02.118Z"
}
}
analysis.sourceType is article, product-page, competitor, video or other. analysis.notableQuotes appears when the page has quotable lines.
Rename a source or edit its notes
PATCH/content/sources/{sourceId}
Send title, notes or both.
Path
| Field | Type | Description |
|---|---|---|
sourceId | UUID required | The source. |
Body
| Field | Type | Description |
|---|---|---|
title | string optional | 1 to 300 characters. |
notes | string or null optional | Up to 5,000 characters. null clears them. |
Request
curl -X PATCH https://app.getprolifik.com/api/v1/content/sources/a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "notes": "Lead with the subject-line tip." }'
Response 200
{
"source": { "id": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d", "notes": "Lead with the subject-line tip.", ... }
}
Delete a content source
DELETE/content/sources/{sourceId}
Deletes the source. Content already made from it stays. Deleting a source that does not exist, or was already deleted, succeeds with alreadyDeleted: true.
Path
| Field | Type | Description |
|---|---|---|
sourceId | UUID required | The source. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/content/sources/a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "alreadyDeleted": false }
Re-analyze a content source
POST/content/sources/{sourceId}/reanalyze
Sets the source back to pending and runs the analysis again. Send a new url to fix a wrong link; with the same URL, the page is not fetched again and only the analysis re-runs. The old analysis stays in the response until the new one lands.
Path
| Field | Type | Description |
|---|---|---|
sourceId | UUID required | The source. |
Body (optional)
| Field | Type | Description |
|---|---|---|
url | string optional | Replace the source URL before analyzing. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/sources/a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d/reanalyze \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 202
{
"source": { "id": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d", "status": "pending", ... }
}
Errors: 402 when the brand is locked; 404 for an unknown source; 429 past 10 a minute (shared with add source); 503 when the worker is unavailable.
Content jobs and pieces
A content job makes a batch of rendered posts, called pieces, in one format. Pieces can be edited, re-rendered and deleted here, and posted from the Publishing endpoints.
List content jobs
GET/content/jobs
Content production jobs, newest first, including jobs started in the app. Pieces are not included; read a single job for those. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID optional | Only this brand's jobs. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/content/jobs?brandId=6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"jobs": [
{
"id": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"contentSourceId": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d",
"format": "caption-card",
"requestedPieces": 5,
"expectedPieces": 5,
"generatedPieces": 5,
"failedPieces": 0,
"status": "ready_for_review",
"deliveryMode": "download",
"includeLogo": true,
"mentionBrand": true,
"createdAt": "2026-10-13T10:15:00.104Z",
"updatedAt": "2026-10-13T10:19:42.877Z",
"completedAt": "2026-10-13T10:19:42.877Z"
}
],
"hasMore": false,
"nextCursor": null
}
Create a content job
POST/content/jobs
Queues a batch of rendered posts in one of the six formats for a brand, and returns at once. Prolifik writes the copy from the brand (and the content source, if you pass one), picks visuals from its library, and renders each piece. Poll Get a content job: status moves from queued to generating, then to ready_for_review when at least one piece is ready (check failedPieces for the rest) or partially_failed when none came out. canceled means you cancelled it. Stop polling on any status other than queued or generating.
Each piece uses one render from the monthly budget; a job that needs more renders than you have left is refused before it starts (see usage). Only one job per format can be queued or generating at a time. Pieces you never download, schedule or post are deleted 14 days after they are made.
Formats: carousel (Image Carousel), kinetic-type (Text Story), scene-swap (Meme Reaction), b-roll (Clip Montage), caption-card (Caption Meme) and talking-head (Video Hook & Demo). talking-head plays a short hook clip, then your own product recording, so it needs demoVideoKey: a video from the media library or a demo upload, at most 100 MB and 60 seconds.
Body
| Field | Type | Description |
|---|---|---|
brandId | UUID required | A brand your plan covers. |
format | string required | One of the six format IDs above. |
count | integer optional | Pieces to make, 1 to 50. Default 1. |
contentSourceId | UUID optional | A ready content source on the same brand to write from. Omit to write from the brand alone. |
includeLogo | boolean optional | Put the brand logo on the pieces. Default false. |
mentionBrand | boolean optional | Name the brand in the copy. Default true. Only carousels use this setting today. |
customTexts | array optional | Your own copy, used word for word instead of AI text. One entry per piece, in order, and no more entries than count. null lets AI write that piece. Carousels take { "slides": [...] } (up to 10 slides); every other format takes { "text": "..." } (up to 2,000 characters). |
assetFilters | object optional | Narrow the library visuals and music. Which keys a format accepts is in the table below; caption-card accepts none. |
deliveryMode | string optional | download (default) or schedule. Recorded as intent only; you still post each piece with Publish a piece. |
demoVideoKey | string see description | talking-head only, and required there. The product recording that plays after the hook. |
hookVideoKey | string optional | talking-head only. Your own hook clip of at most 15 seconds, for example an AI Studio talking head from the media library. Omit it and each piece gets a library hook. |
assetFilters
| Field | Type | Description |
|---|---|---|
theme | string optional | Formats: carousel, b-roll. A library category for the format, up to 64 characters. An unknown theme returns 400 with the valid ones. |
personTags | array optional | Formats: carousel, kinetic-type, b-roll. Woman, Man, Animal, Landscape/Building. |
audioMood | string optional | Formats: kinetic-type, b-roll, talking-head. none (silent), upbeat, chill, cinematic, motivational or dramatic. |
memePersonTags | array optional | Formats: scene-swap. Only meme clips tagged Woman or Man. |
backgroundPersonTags | array optional | Formats: scene-swap. Only background clips with these tags: Woman, Man, Animal, Landscape/Building. |
memeReactions | array optional | Formats: scene-swap. Pick meme clips by reaction, and write the caption to set it up: smug_vindication, caught_in_the_act, instant_refusal, blank_incomprehension, quiet_trauma, two_block_reaction. When no clip fits, the whole pool is used. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/jobs \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-acme-captions-1013" \
-d '{
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"contentSourceId": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d",
"format": "caption-card",
"count": 5,
"includeLogo": true
}'
Response 202
{
"job": {
"id": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"format": "caption-card",
"requestedPieces": 5,
"expectedPieces": null,
"generatedPieces": 0,
"failedPieces": 0,
"status": "queued",
...
}
}
Errors: 400 for a bad field or a talking-head video that fails its checks (the message says which); 402 quota_exceeded when the render budget is short or the brand is locked by your plan; 404 for an unknown brand or source; 409 when the source is not ready, a job for this format is already running, or the monthly job cap is reached; 429 past 5 jobs a minute; 503 when the worker is unavailable.
Get a content job
GET/content/jobs/{jobId}
The job and every piece it has produced so far, oldest first, with signed media links. A job can move back to generating for a while after you regenerate one of its pieces.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The job. |
Request
curl https://app.getprolifik.com/api/v1/content/jobs/f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"job": {
"id": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"contentSourceId": "a41d7c3e-8b2f-4e6a-9c1d-3f5e7a9b1c2d",
"format": "caption-card",
"requestedPieces": 5,
"expectedPieces": 5,
"generatedPieces": 5,
"failedPieces": 0,
"status": "ready_for_review",
"deliveryMode": "download",
"includeLogo": true,
"mentionBrand": true,
"createdAt": "2026-10-13T10:15:00.104Z",
"updatedAt": "2026-10-13T10:19:42.877Z",
"completedAt": "2026-10-13T10:19:42.877Z"
},
"pieces": [
{
"id": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"jobId": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"format": "caption-card",
"status": "ready",
"failureReason": null,
"reviewStatus": "pending",
"script": {
"hook": "When the client says the invoice is \"in the system\" for the third week:",
"body": "",
"cta": "",
"format": "caption-card"
},
"videoUrl": "https://...",
"slideUrls": null,
"thumbnailUrl": "https://...",
"includeLogo": true,
"origin": "pulse",
"consumedAt": null,
"expiresAt": "2026-10-27T10:15:00.104Z",
"createdAt": "2026-10-13T10:15:00.104Z"
}
]
}
Cancel a content job
DELETE/content/jobs/{jobId}
Cancels a job that is queued or generating. Cancelling a job that is already cancelled also returns ok. Any other status returns 409.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The job. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/content/jobs/f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true }
List pieces
GET/content/pieces
Every generated piece on the account, newest first: pieces from content jobs, from Content Studio in the dashboard, and cloning variations. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID optional | Only pieces from this brand's jobs. |
format | string optional | Exact format ID, for example carousel. |
status | string optional | pending, pending_render, rendering, ready or failed. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/content/pieces?status=ready&limit=20" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"pieces": [
{
"id": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"jobId": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"format": "caption-card",
"status": "ready",
"failureReason": null,
"reviewStatus": "pending",
"script": {
"hook": "When the client says the invoice is \"in the system\" for the third week:",
"body": "",
"cta": "",
"format": "caption-card"
},
"videoUrl": "https://...",
"slideUrls": null,
"thumbnailUrl": "https://...",
"includeLogo": true,
"origin": "pulse",
"consumedAt": null,
"expiresAt": "2026-10-27T10:15:00.104Z",
"createdAt": "2026-10-13T10:15:00.104Z"
}
],
"hasMore": true,
"nextCursor": "MjAyNi0xMC0xM1QxMDoxNTowMC4xMDQ1NjdafDFlM2E1YzdiLTlkMmYtNGI2ZS04YTFjLTNlNWY3YjlkMmE0Yw"
}
Get a piece
GET/content/pieces/{pieceId}
One piece with its script and signed media links. A video piece has videoUrl; a carousel has slideUrls, one PNG per slide. Links expire within the hour, so fetch the piece again rather than storing them.
reviewStatus is pending, approved or rejected. consumedAt is set once the piece has been downloaded in the app, scheduled or posted; until then expiresAt says when an unused job piece is deleted.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | The piece. |
Request
curl https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"piece": {
"id": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"jobId": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e",
"format": "caption-card",
"status": "ready",
"failureReason": null,
"reviewStatus": "pending",
"script": {
"hook": "When the client says the invoice is \"in the system\" for the third week:",
"body": "",
"cta": "",
"format": "caption-card"
},
"videoUrl": "https://...",
"slideUrls": null,
"thumbnailUrl": "https://...",
"includeLogo": true,
"origin": "pulse",
"consumedAt": null,
"expiresAt": "2026-10-27T10:15:00.104Z",
"createdAt": "2026-10-13T10:15:00.104Z"
}
}
Edit a piece and render it again
PATCH/content/pieces/{pieceId}
Changes the copy or logo setting and queues a new render, which uses one render from the budget. Returns the piece with status: "pending_render"; the old media links stay in the response until the new render lands. A piece that has been downloaded, scheduled or posted cannot be edited (409), and neither can one that is rendering.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | The piece. |
Body
| Field | Type | Description |
|---|---|---|
hook | string optional | Up to 2,000 characters; 220 for caption-card. |
body | string optional | Up to 2,000 characters. |
cta | string optional | Up to 300 characters. |
slides | array of strings optional | Carousel slide text: up to 10 slides of 2,000 characters. |
includeLogo | boolean optional | Show or hide the brand logo. |
Request
curl -X PATCH https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: edit-1e3a5c7b-1" \
-d '{ "hook": "When the client says the invoice is \"being processed\" for week three:" }'
Response 202
{
"piece": { "id": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c", "status": "pending_render", ... }
}
Errors: 402 when the render budget is used up or the brand is locked; 409 when the piece is consumed or rendering; 503 when the render worker is unavailable (the piece is marked failed and keeps the new script, so you can retry it).
Delete a piece
DELETE/content/pieces/{pieceId}
Deletes the piece, its render and its publish records. Queued posts for it are cancelled first; if the posting provider refuses a cancel, nothing is deleted and you get 502. Posts that already went out stay up on the platforms. A piece that does not exist returns alreadyDeleted: true. A job piece that is still being made returns 409.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | The piece. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "alreadyDeleted": false, "refundedPublishes": 1 }
refundedPublishes counts queued posts that were cancelled and returned to your monthly video quota.
Regenerate a failed piece
POST/content/pieces/{pieceId}/regenerate
Replaces a failed piece from a content job with a newly written script and new library visuals. Uses one render. The replacement is a new piece with a new ID, and the old ID returns 404 once it is gone: read the job to find the new piece. To render the same script again instead, use Retry a failed render. No body needed.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | A failed piece made by a content job. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c/regenerate \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Idempotency-Key: regen-1e3a5c7b-1"
Response 202
{ "queued": true, "pieceId": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c", "jobId": "f2c4e6a8-9b1d-4f3a-8c5e-7d9f1b3a5c6e" }
Returns 409 for a piece that has not failed or did not come from a content job.
Retry a failed render
POST/content/pieces/{pieceId}/retry
Renders a failed piece's existing script again, under the same ID. Uses one render. Works on any failed piece. No body needed.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | A failed piece. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c/retry \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Idempotency-Key: retry-1e3a5c7b-1"
Response 202
{
"piece": { "id": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c", "status": "pending", ... }
}
Returns 409 for a piece that has not failed, 503 when the render worker is unavailable.
Publishing
Post pieces to the Instagram, TikTok, YouTube and X accounts connected to a brand, now or on a schedule, and manage posts that have not gone out yet. Accounts are connected in the dashboard, not through the API. Clips are posted from the Clipper endpoints, and cloned variations can be spread across accounts with Distribute.
List publish destinations
GET/publish-destinations
The social accounts connected to a brand that your plan covers (Lite covers 1 account per platform, Starter 3, Pro and Enterprise all). Without brandId it uses the active brand. A brand you do not own, or one your plan has locked, returns an empty list. An account that needs reconnecting is still listed, and posting to it returns 400.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID optional | The brand. |
Request
curl "https://app.getprolifik.com/api/v1/publish-destinations?brandId=6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"destinations": [
{ "id": "8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "platform": "tiktok", "accountName": "acmehq" },
{ "id": "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b", "platform": "instagram", "accountName": "acme" }
]
}
platform is instagram, tiktok, youtube or x. Use the id values as connectionIds.
Publish or schedule a piece
POST/content/pieces/{pieceId}/publish
Creates a real post on one or more connected accounts, now or at scheduledAt. Needs the publish:write scope, which is separate from content:write so a key can make content without being able to post it. The piece must be ready.
The first time a piece is scheduled or posted it counts as one of the plan's monthly videos (20 on Lite, 100 on Starter, 500 on Pro). A piece can have one live post at a time: while a post of it is scheduled or posting, another publish returns 409. The posted caption is your caption, a blank line, then the hashtags, each with a # added if missing. On X, the caption is cut to fit 280 characters and a carousel keeps its first 4 images.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | A ready piece. |
Body
| Field | Type | Description |
|---|---|---|
brandId | UUID required | The piece's brand. |
connectionIds | array of UUIDs required | 1 to 10 destination IDs from List publish destinations. A cloned variation posts to exactly one account. |
caption | string required | Up to 2,200 characters. Leave room for the hashtags: Instagram caps the whole caption at 2,200. |
hashtags | array of strings optional | Up to 30, each up to 100 characters. |
scheduledAt | ISO 8601 timestamp optional | With Z or an offset, between one minute and one year ahead. Omit to post now. |
aiGenerated | boolean optional | Mark the post as AI-generated: TikTok's own AI label, and a "Made with AI" line on other platforms unless the caption already says so. Defaults to true when the piece uses AI Studio or AI influencer media. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/pieces/1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c/publish \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: publish-1e3a5c7b-oct14" \
-d '{
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"connectionIds": ["8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b"],
"caption": "Third week of \"it'"'"'s in the system\".",
"hashtags": ["freelance", "invoicing"],
"scheduledAt": "2026-10-14T13:00:00Z"
}'
Response 201
{
"publish": {
"id": "4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f",
"contentPieceId": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"platforms": ["tiktok", "instagram"],
"connectionIds": ["8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b"],
"caption": "Third week of \"it's in the system\".",
"hashtags": ["freelance", "invoicing"],
"scheduledAt": "2026-10-14T13:00:00.000Z",
"postedAt": null,
"status": "scheduled",
"platformPostIds": null,
"aiGenerated": false,
"createdAt": "2026-10-13T10:25:31.660Z"
}
}
A post-now request comes back as posting. Errors: 400 for a bad time, an account that is not on this brand or needs reconnecting; 402 quota_exceeded when the monthly videos are used up or a brand or account is locked by your plan; 404 for a piece that is not on this brand; 409 when the piece is not ready or already has a live post; 503 when the publishing worker is unavailable.
List content posts
GET/content/publishes
Content posts, newest created first. Clip posts are listed under clip publish records. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
pieceId | UUID optional | Only posts of this piece. |
status | string optional | scheduled, posting, posted, partial, failed or cancelled. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/content/publishes?status=scheduled" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"publishes": [
{
"id": "4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f",
"contentPieceId": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"platforms": ["tiktok", "instagram"],
"connectionIds": ["8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b"],
"caption": "Third week of \"it's in the system\".",
"hashtags": ["freelance", "invoicing"],
"scheduledAt": "2026-10-14T13:00:00.000Z",
"postedAt": null,
"status": "scheduled",
"platformPostIds": null,
"aiGenerated": false,
"createdAt": "2026-10-13T10:25:31.660Z"
}
],
"hasMore": false,
"nextCursor": null
}
Get a content post
GET/content/publishes/{publishId}
One post and its status: scheduled, posting, posted, partial (some accounts posted, others failed), failed or cancelled. postedAt is set once it went out. platformPostIds holds what the posting provider reported, including each platform's post ID and URL on success or the error on failure.
Path
| Field | Type | Description |
|---|---|---|
publishId | UUID required | The post. |
Request
curl https://app.getprolifik.com/api/v1/content/publishes/4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"publish": {
"id": "4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f",
"contentPieceId": "1e3a5c7b-9d2f-4b6e-8a1c-3e5f7b9d2a4c",
"platforms": ["tiktok", "instagram"],
"connectionIds": ["8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b"],
"caption": "Third week of \"it's in the system\".",
"hashtags": ["freelance", "invoicing"],
"scheduledAt": "2026-10-14T13:00:00.000Z",
"postedAt": null,
"status": "scheduled",
"platformPostIds": null,
"aiGenerated": false,
"createdAt": "2026-10-13T10:25:31.660Z"
}
}
Edit a queued post's caption
PATCH/content/publishes/{publishId}
Changes the caption and hashtags of a post that has not gone out yet. Send both caption and hashtags in every update: the caption at the platform is rebuilt from the fields in the request, so a field you leave out is dropped there. Posts that went out, even partly, or were cancelled return 409. Failed posts can be edited.
Path
| Field | Type | Description |
|---|---|---|
publishId | UUID required | The post. |
Body
| Field | Type | Description |
|---|---|---|
caption | string see description | Up to 2,200 characters. |
hashtags | array of strings see description | Up to 30, each up to 100 characters. |
Request
curl -X PATCH https://app.getprolifik.com/api/v1/content/publishes/4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "caption": "Week three of \"it'"'"'s in the system\".", "hashtags": ["freelance", "invoicing"] }'
Response 200
{
"publish": { "id": "4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f", "caption": "Week three of \"it's in the system\".", ... }
}
Returns 502 when the posting provider rejects the change; the original caption is kept.
Cancel a queued post
DELETE/content/publishes/{publishId}
Cancels a post that has not gone out. refunded is true when the monthly video it used came back to your quota. Cancelling a post that is already cancelled returns alreadyCancelled: true. Posts that went out, even partly, return 409.
Path
| Field | Type | Description |
|---|---|---|
publishId | UUID required | The post. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/content/publishes/4b6d8f1a-3c5e-4a7b-9d2f-6e8a1c3b5d7f \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "refunded": true, "alreadyCancelled": false }
Returns 502 when the posting provider could not cancel it; the post is left as it was, so try again.
Usage
How much of the month's videos and renders are left. Clip minutes and AI credits have their own endpoints: clip minutes and AI credits.
Get plan usage
GET/usage
This billing month's numbers. videos counts videos you keep: downloaded in the app, scheduled or published, each piece once (20 a month on Lite, 100 on Starter, 500 on Pro). renders counts every successful render, including retries, regenerations and edits (1,000 on Lite, 2,000 on Starter, 4,000 on Pro). A content job that needs more renders than renders.remaining is refused with 402.
limit and remaining are null when unlimited (Enterprise), and 0 when the account has no paid plan.
Request
curl https://app.getprolifik.com/api/v1/usage \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"plan": "starter",
"trial": null,
"videos": {
"used": 37,
"limit": 100,
"remaining": 63,
"resetsAt": "2026-11-02T08:14:51.000Z",
"note": "Counts videos you keep: downloaded in the app, scheduled or published. Each piece counts once."
},
"renders": {
"used": 412,
"limit": 2000,
"remaining": 1588,
"resetsAt": "2026-11-02T08:14:51.000Z",
"note": "Counts every successful render: job pieces, retries, regenerations, and Studio saves and downloads. Cloning is paid in AI credits instead."
}
}
trial is always null for API keys, which need a paid plan.
Media
Your media library, and upload links for the recordings the Video Hook & Demo format plays.
List the media library
GET/content/media
Videos (the default) or images in your Prolifik media library, newest first: files you uploaded in the app, demo recordings saved from Content Studio, and AI Studio outputs when the key also has studio:read. A video with usableForHookDemo: true can go straight into a content job as demoVideoKey or hookVideoKey, so a recording that is already in the app never needs uploading again.
previewUrl and thumbnailUrl are signed and expire within the hour. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
type | string optional | video (default) or image. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/content/media?type=video&limit=10" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"media": [
{
"id": "5c8e1f3a-7b2d-4a9e-8f1c-2d3e4f5a6b7c",
"name": "dashboard-demo.mp4",
"key": "uploads/<your user id>/dashboard-demo.mp4",
"type": "video",
"category": "demo",
"description": null,
"usableForHookDemo": true,
"previewUrl": "https://...",
"thumbnailUrl": "https://...",
"createdAt": "2026-10-10T15:02:33.871Z"
}
],
"hasMore": false,
"nextCursor": null
}
category is upload, demo, ai-studio or null. Files sent through Create a demo upload URL do not appear here; use the key you got back.
Create a demo upload URL
POST/content/uploads
For the Video Hook & Demo format (talking-head). PUT the video to uploadUrl within 30 minutes with the returned contentType as the Content-Type header (it is part of the signature) and no Authorization header. Then pass key to Create a content job as demoVideoKey or hookVideoKey.
The file is checked when the job is created: an mp4, mov or webm of at most 100 MB, a demo of at most 60 seconds and a hook of at most 15 seconds. A file that fails the check is deleted. The render replaces the recording's own sound with a library music track.
Body
| Field | Type | Description |
|---|---|---|
contentType | string required | video/mp4, video/quicktime or video/webm. |
Request
curl -X POST https://app.getprolifik.com/api/v1/content/uploads \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "contentType": "video/mp4" }'
# then upload the file
curl -X PUT -H "Content-Type: video/mp4" --upload-file demo.mp4 "<uploadUrl>"
Response 201
{
"key": "uploads/<your user id>/api-0b6f2c4e-1d3a-4f5b-9c7e-8a2d4f6b1e3c.mp4",
"uploadUrl": "https://...",
"contentType": "video/mp4",
"expiresInSeconds": 1800,
"maxBytes": 104857600
}
Limited to 20 upload URLs a minute per key.
AI Studio
Generate images, short videos and talking-head videos with AI models. Generations spend AI credits (250 a month on Starter, 1,000 on Pro, plus any packs you bought), charged when a job is accepted and refunded if it fails. Creating needs Starter or higher; reading works on any paid plan.
List AI Studio models and prices
GET/studio/models
Every image, video and talking-head model you can pick, with the aspect ratios, durations and resolutions it accepts and what it costs in AI credits. Read this before you queue a generation: modelKey and params must match a model's entry here. Prices are per image (creditsPerImage) or per second of video (creditsPerSecond, by resolution where a model offers a choice, and at the audio rate when generateAudio is on).
Request
curl https://app.getprolifik.com/api/v1/studio/models \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"models": [
{
"key": "seedance-2",
"label": "Seedance 2.0",
"kind": "video",
"aspectRatios": ["9:16", "3:4", "1:1", "4:3", "16:9"],
"durationsSec": [4, 5, 6, 7, 8, 9, 10],
"resolutions": ["480p", "720p", "1080p"],
"defaultResolution": "1080p",
"supportsAspectRatio": true,
"supportsGeneratedAudio": true,
"supportsNumImages": false,
"requiresReferenceImage": false,
"creditsPerImage": null,
"creditsPerSecond": 68,
"creditsPerSecondByResolution": { "480p": 30, "720p": 30, "1080p": 68 },
"creditsPerSecondWithAudio": null,
"creditsPerSecondWithAudioByResolution": null,
"maxDurationSecByResolution": null
}
],
"defaultVideoModelKey": "kling-v3-pro",
"talkingHeadModelKey": "seedance-2-talking-head",
"talkingHeadLanguages": ["English", "Spanish", "French", "..."],
"influencerBaseImageCredits": 4
}
Fields that do not apply to a model are null. maxDurationSecByResolution is set where a resolution caps the length.
Get the AI credit balance
GET/studio/credits
The same numbers as Settings > AI Credits: balance, the plan's monthly allowance (250 on Starter, 1,000 on Pro, none on Lite), when the period ends, monthly versus carried-over credits, and the 20 most recent ledger entries. Enterprise reports balance: -1 with unlimited: true. Credit packs are bought in the dashboard.
Request
curl https://app.getprolifik.com/api/v1/studio/credits \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"credits": {
"balance": 214,
"unlimited": false,
"plan": "starter",
"allowance": 250,
"periodEnd": "2026-11-02T08:14:51.000Z",
"canBuyPacks": true,
"monthly": { "granted": 250, "used": 36, "remaining": 214 },
"carried": { "remaining": 0, "purchased": 0 }
},
"recentActivity": [
{
"id": "1b3d5f7a-9c2e-4a6b-8d1f-3e5a7c9b2d4f",
"type": "spend-image",
"delta": -8,
"label": "Image generation",
"detail": null,
"createdAt": "2026-10-13T10:02:11.402Z"
}
]
}
Ledger type values include monthly-grant, purchase, refund, spend-image, spend-video, spend-influencer-creation and refund-failed-gen.
Create a reference image upload URL
POST/studio/uploads
For image edits and image-to-video. PUT the image to uploadUrl within 15 minutes, then pass key as params.imageKey when you queue a generation. Needs Starter or higher.
Body
| Field | Type | Description |
|---|---|---|
contentType | string required | image/png, image/jpeg or image/webp. |
Request
curl -X POST https://app.getprolifik.com/api/v1/studio/uploads \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "contentType": "image/png" }'
# then upload the image
curl -X PUT -H "Content-Type: image/png" --upload-file product.png "<uploadUrl>"
Response 201
{
"key": "studio/<your user id>/uploads/9f1b3d5a-7c2e-4b6d-8a1f-3c5e7b9d2a4c.png",
"uploadUrl": "https://...",
"expiresInSeconds": 900
}
Limited to 20 upload URLs a minute per key.
List AI Studio generations
GET/studio/generations
Image, video and talking-head generations, newest first. While hasMore is true, pass nextCursor as before for the next page.
Query
| Field | Type | Description |
|---|---|---|
status | string optional | queued, running, complete or failed. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/studio/generations?status=complete&limit=10" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"generations": [
{
"id": "e3b1c9a7-5d2f-4c8e-a1b3-9f7d5e3c1a2b",
"influencerId": null,
"kind": "image",
"modelKey": "gpt-image-2",
"prompt": "A freelancer at a sunny kitchen table, laptop open, relieved smile",
"params": { "aspectRatio": "9:16", "numImages": 1 },
"status": "complete",
"outputs": [
{ "key": "studio/<your user id>/outputs/...png", "contentType": "image/png", "url": "https://..." }
],
"creditsCharged": 8,
"error": null,
"createdAt": "2026-10-13T10:02:11.402Z",
"completedAt": "2026-10-13T10:02:39.917Z"
}
],
"hasMore": false,
"nextCursor": null
}
Queue an AI Studio generation
POST/studio/generations
Queues an image, video or talking-head generation and returns it with status: "queued". Credits are charged when the job is accepted and refunded if it fails. Poll Get a generation until status is complete or failed. Needs Starter or higher.
What each kind needs: image needs a prompt; edit models also need params.imageKey or an influencerId. video needs params.imageKey or an influencerId as the source image, and takes params.motionPrompt. talking-head needs params.script, an image or influencer, and takes params.language (one of talkingHeadLanguages). For video and talking-head, always send params.durationSec.
Body
| Field | Type | Description |
|---|---|---|
kind | string required | image, video or talking-head. |
modelKey | string required | A key from List AI Studio models of the same kind. |
prompt | string see description | Up to 5,000 characters. Required for images. |
influencerId | UUID optional | Use an AI influencer's portrait as the reference image. The influencer needs a finished portrait. |
params | object required | Model settings. See the table below. An unknown key returns 400. |
Params
| Field | Type | Description |
|---|---|---|
aspectRatio | string optional | One of the model's aspectRatios. |
resolution | string optional | 480p, 720p or 1080p, where the model offers a choice. |
numImages | integer optional | 1 or 4, on models with supportsNumImages. |
template | string optional | none, iphone-selfie or shocked. |
enhance | boolean optional | Images only: rewrite the prompt into a detailed photo brief first. The rewritten prompt is added to params.enhancedPrompt. |
imageKey | string optional | A key from a reference upload, or an earlier output's key. |
motionPrompt | string optional | Video only: how things should move, up to 5,000 characters. |
durationSec | integer optional | Video and talking-head: one of the model's durationsSec. |
generateAudio | boolean optional | Video models with supportsGeneratedAudio; billed at the audio rate. |
script | string optional | Talking-head: what the person says, up to 5,000 characters. |
language | string optional | Talking-head: one of talkingHeadLanguages. Default English. |
captions | boolean optional | Talking-head: burn in captions. Default true. |
Request
curl -X POST https://app.getprolifik.com/api/v1/studio/generations \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gen-kitchen-table-001" \
-d '{
"kind": "image",
"modelKey": "gpt-image-2",
"prompt": "A freelancer at a sunny kitchen table, laptop open, relieved smile",
"params": { "aspectRatio": "9:16", "numImages": 1 }
}'
Response 202
{
"generation": { "id": "e3b1c9a7-5d2f-4c8e-a1b3-9f7d5e3c1a2b", "kind": "image", "status": "queued", "outputs": [], "creditsCharged": 8, ... }
}
Errors: 400 when a parameter does not fit the model (the message says which); 402 insufficient_credits with needed and balance; 402 subscription_required on Lite; 404 for an unknown influencer; 409 when the influencer has no portrait yet; 429 past 5 a minute; 503 when the worker is unavailable (credits refunded).
Get a generation
GET/studio/generations/{generationId}
One generation's status. When it is complete, outputs holds a signed url per file; links expire within the hour, so read the generation again for fresh ones. A failed generation keeps creditsCharged at what was charged and has a separate refund entry in the credit ledger. modelKey is the model that ran, which for an image with a reference is the model's edit variant.
Path
| Field | Type | Description |
|---|---|---|
generationId | UUID required | The generation. |
Request
curl https://app.getprolifik.com/api/v1/studio/generations/e3b1c9a7-5d2f-4c8e-a1b3-9f7d5e3c1a2b \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"generation": {
"id": "e3b1c9a7-5d2f-4c8e-a1b3-9f7d5e3c1a2b",
"influencerId": null,
"kind": "image",
"modelKey": "gpt-image-2",
"prompt": "A freelancer at a sunny kitchen table, laptop open, relieved smile",
"params": { "aspectRatio": "9:16", "numImages": 1 },
"status": "complete",
"outputs": [
{ "key": "studio/<your user id>/outputs/...png", "contentType": "image/png", "url": "https://..." }
],
"creditsCharged": 8,
"error": null,
"createdAt": "2026-10-13T10:02:11.402Z",
"completedAt": "2026-10-13T10:02:39.917Z"
}
}
Influencers
A reusable AI person, with a portrait you can use as the reference image in AI Studio generations. Creating and regenerating need Starter or higher and cost AI credits.
List AI influencers
GET/influencers
Your reusable AI influencers, newest first. Not paginated. status is pending_base while the portrait is being made, then base_ready, or failed.
Request
curl https://app.getprolifik.com/api/v1/influencers \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"influencers": [
{
"id": "7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"name": "Maya",
"gender": "female",
"age": 29,
"ethnicity": "Lithuanian",
"details": "Freckles, short dark hair, casual denim jacket",
"status": "base_ready",
"baseImageUrl": "https://...",
"libraryImages": [],
"creditsSpent": 4,
"error": null,
"createdAt": "2026-10-12T14:20:05.118Z",
"updatedAt": "2026-10-12T14:20:41.730Z"
}
]
}
Create an AI influencer
POST/influencers
Creates an influencer and generates its portrait, for 4 AI credits. Poll Get an influencer until status is base_ready, then use its ID as influencerId in generations. Needs Starter or higher.
Body
| Field | Type | Description |
|---|---|---|
name | string required | 1 to 100 characters. |
gender | string required | male or female. |
age | integer optional | 18 to 60. Default 25. |
ethnicity | string optional | Up to 100 characters. |
details | string optional | Look and style, up to 1,000 characters. |
brandId | UUID optional | Defaults to the active brand. |
Request
curl -X POST https://app.getprolifik.com/api/v1/influencers \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: influencer-maya-01" \
-d '{ "name": "Maya", "gender": "female", "age": 29, "details": "Freckles, short dark hair, casual denim jacket" }'
Response 202
{
"influencer": { "id": "7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b", "name": "Maya", "status": "pending_base", "baseImageUrl": null, ... }
}
Errors: 402 insufficient_credits or subscription_required; 404 for an unknown brand; 429 past 5 influencer changes a minute (create, regenerate and delete share the limit); 503 when the worker is unavailable (credits refunded).
Get an AI influencer
GET/influencers/{influencerId}
One influencer, with a signed baseImageUrl once the portrait exists. The link expires within the hour.
Path
| Field | Type | Description |
|---|---|---|
influencerId | UUID required | The influencer. |
Request
curl https://app.getprolifik.com/api/v1/influencers/7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"influencer": {
"id": "7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b",
"brandId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"name": "Maya",
"gender": "female",
"age": 29,
"ethnicity": "Lithuanian",
"details": "Freckles, short dark hair, casual denim jacket",
"status": "base_ready",
"baseImageUrl": "https://...",
"libraryImages": [],
"creditsSpent": 4,
"error": null,
"createdAt": "2026-10-12T14:20:05.118Z",
"updatedAt": "2026-10-12T14:20:41.730Z"
}
}
Delete an AI influencer
DELETE/influencers/{influencerId}
Deletes the influencer and its stored images. Generations made with it stay. Credits are not refunded. Deleting one that does not exist succeeds with alreadyDeleted: true.
Path
| Field | Type | Description |
|---|---|---|
influencerId | UUID required | The influencer. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/influencers/7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "alreadyDeleted": false }
Returns 409 while the portrait is still being generated.
Regenerate an influencer portrait
POST/influencers/{influencerId}/regenerate
Replaces the portrait with a newly generated person, for 4 AI credits. Works on influencers that are base_ready or failed. Send a JSON body, even if it is just {}.
Path
| Field | Type | Description |
|---|---|---|
influencerId | UUID required | The influencer. |
Body
| Field | Type | Description |
|---|---|---|
instructions | string optional | What to change, up to 1,000 characters. |
Request
curl -X POST https://app.getprolifik.com/api/v1/influencers/7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b/regenerate \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: influencer-maya-regen-01" \
-d '{ "instructions": "Older, around 35, glasses" }'
Response 202
{ "ok": true, "influencerId": "7a9c1e3b-2d4f-4a6c-8e1b-3d5f7a9c1e2b" }
Returns 409 while a portrait is already being generated, 402 when credits run short.
Cloning
Rebuild a TikTok or Instagram photo carousel or text-over-video post as variations in your brand's voice, translate them, and schedule them across accounts. Every cloning endpoint, reads included, needs Starter or higher; work is paid in AI credits.
Quote a clone
POST/cloning/estimate
What cloning a post would cost, without downloading or charging anything. The quote is 10 credits for download and analysis plus 4 credits per new image. Before the post is read, a photo post's slide count is unknown, so it is quoted at six slides and the response sets assumedSlides. Needs Starter or higher.
Body
| Field | Type | Description |
|---|---|---|
url | string required | A single TikTok or Instagram post link. |
variations | integer optional | 1 to 8. Default 3. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/estimate \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://www.tiktok.com/@acme/photo/7420000000000000000", "variations": 3 }'
Response 200
{
"credits": 82,
"breakdown": [
{ "label": "Download and analysis", "credits": 10 },
{ "label": "3 variations × 6 slides × 4 cr (estimated)", "credits": 72 }
],
"assumedSlides": 6
}
List cloning jobs
GET/cloning/jobs
Cloning jobs, newest first. While hasMore is true, pass nextCursor as before for the next page. Needs Starter or higher.
Query
| Field | Type | Description |
|---|---|---|
brandId | UUID optional | Only this brand's jobs. |
status | string optional | pending, ingesting, analysing, transforming, ready or failed. |
before | string optional | The previous page's nextCursor. An ISO 8601 timestamp also works; see Pagination. |
limit | integer optional | 1 to 100. Default 50. |
Request
curl "https://app.getprolifik.com/api/v1/cloning/jobs?status=ready" \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"jobs": [
{
"id": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f",
"brandProfileId": "6f0c2d9e-3a1b-4c7d-8e5f-1a2b3c4d5e6f",
"url": "https://www.tiktok.com/@somecreator/photo/7420000000000000000",
"platform": "tiktok",
"sourceKind": "slideshow",
"sourceFormat": "slideshow",
"status": "ready",
"errorMessage": null,
"createdAt": "2026-10-13T11:00:04.210Z",
"updatedAt": "2026-10-13T11:04:52.771Z",
"creditsCharged": 82,
"preview": { "coverUrl": "https://...", "title": "3 invoice mistakes", "uploader": "somecreator", "views": 184000 },
"progress": null,
"variationCount": 3
}
],
"hasMore": false,
"nextCursor": null
}
Clone a TikTok or Instagram post
POST/cloning/jobs
Paste a TikTok or Instagram link to a photo carousel or a text-over-video post, and Prolifik rebuilds it as variations in the brand's voice: rewritten copy, slightly changed visuals, the brand's logo. Returns the job with status: "pending"; poll Get a cloning job until it is ready or failed.
The estimated credits are reserved up front. You pay for the analysis and each variation that is delivered; the rest is refunded when the job settles. A brand can have up to 50 cloning jobs that have not failed. Needs Starter or higher.
Body
| Field | Type | Description |
|---|---|---|
url | string required | A single TikTok or Instagram post link. |
brandId | UUID optional | Whose voice and logo to use. Defaults to the active brand. |
variations | integer optional | 1 to 8. Default 3. |
mentionBrand | boolean optional | Name the brand in the rewritten copy, not only in the logo. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/jobs \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clone-7420000000000000000" \
-d '{ "url": "https://www.tiktok.com/@somecreator/photo/7420000000000000000", "variations": 3 }'
Response 202
{
"job": {
"id": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f",
"status": "pending",
"creditsCharged": 0,
"options": { "variations": 3 },
"source": null,
"analysis": null,
"variations": [],
...
}
}
Errors: 400 for a profile, story or other link that is not a single post; 402 insufficient_credits; 404 for an unknown brand; 409 at 50 jobs for the brand; 429 past 10 in 10 minutes; 503 when the worker is unavailable (credits refunded).
Get a cloning job and its variations
GET/cloning/jobs/{jobId}
The job, the source post as Prolifik read it (source, with signed media links), the analysis, and every variation built so far. A variation is a content piece: save it to the Library with Save a variation to schedule or post it like any other piece, or spread several across accounts with Distribute.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Request
curl https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{
"job": {
"id": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f",
"status": "ready",
"creditsCharged": 82,
"progress": null,
"variationCount": 3,
"source": { "kind": "slideshow", "platform": "tiktok", "caption": "...", "images": [{ "index": 0, "url": "https://..." }] },
"analysis": { ... },
"variations": [
{
"pieceId": "d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c",
"variationIndex": 0,
"format": "carousel",
"hook": "3 invoice mistakes that cost freelancers a month of rent",
"thumbnailUrl": "https://...",
"finalVideoUrl": null,
"slideUrls": ["https://...", "https://..."],
"status": "ready",
"reviewStatus": "pending",
"language": null,
"localizedFrom": null,
"translatable": true,
"translateBlockedReason": null,
"caption": "Stop losing money on late invoices.",
"hashtags": ["#freelance", "#invoicing"],
"schedules": []
}
],
...
}
}
While a job runs, progress is { step, total, label }. A carousel variation has slideUrls; any other format has finalVideoUrl. translatable says whether Translate variations can take it now.
Delete a cloning job
DELETE/cloning/jobs/{jobId}
Deletes a finished job (ready or failed); a running job returns 409. Anything still reserved is refunded first. The variations are kept as pieces, detached from the job.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Request
curl -X DELETE https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "jobId": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f" }
Add variations to a job
POST/cloning/jobs/{jobId}/variations
Builds more variations on a job that is already analyzed, without downloading and reading the post again. The job goes back to transforming; poll it until ready. The reservation includes the 10-credit analysis line, which is refunded when the job settles because the analysis is not charged again.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Body
| Field | Type | Description |
|---|---|---|
variations | integer required | 1 to 8. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f/variations \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clone-more-b8d2f4a6-1" \
-d '{ "variations": 2 }'
Response 202
{ "ok": true, "jobId": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f", "variations": 2 }
Returns 409 while the job is still running, before it has been analyzed, or for a kind of post that cannot be cloned yet.
Translate variations
POST/cloning/jobs/{jobId}/localize
Each (variation, language) pair becomes a new variation on the same job, built on the original's background with no new images. App, product and brand names stay as they are; the caption and hashtags are translated. Costs 3 credits per translation, charged only for translations whose render finishes. At most 20 translations per request.
Languages: es (Spanish), pt-BR (Portuguese, Brazil), fr, de, it, nl, pl, tr, id (Indonesian). Pairs that already exist are skipped; if all of them exist, the request returns 409.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Body
| Field | Type | Description |
|---|---|---|
languages | array of strings required | One or more language codes from the list above. |
pieceIds | array of UUIDs optional | Up to 50 variations to translate. Omit to translate every finished original variation. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f/localize \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clone-es-de-b8d2f4a6" \
-d '{ "languages": ["es", "de"] }'
Response 202
{
"ok": true,
"jobId": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f",
"translations": 2,
"credits": 6,
"items": [
{ "pieceId": "d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c", "language": "es", "variationIndex": 3 },
{ "pieceId": "d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c", "language": "de", "variationIndex": 4 }
]
}
Translated variations carry language and localizedFrom. A variation made before translation existed, edited since it was cloned, or itself a translation returns 409.
Retry a failed cloning job
POST/cloning/jobs/{jobId}/retry
Starts a failed job with no variations again from the link. Returns 409 for a job that is not failed, one that already has variations (add variations instead), or a post Prolifik cannot clone yet, since a retry would charge again and fail the same way.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f/retry \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 202
{ "ok": true, "jobId": "b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f" }
Schedule variations across accounts
POST/cloning/jobs/{jobId}/distribute
Creates real posts. Spreads different variations of one job across the brand's social accounts at staggered times, so no two accounts post the same variation and each account gets at most one post from the job per day. Each post uses the variation's own caption and hashtags. Needs publish:write and a Starter or higher plan.
Send either an explicit plan, or socialAccountIds and startAt and let Prolifik plan it with the same rules the dashboard uses. The whole plan is checked before anything is scheduled: ownership, rendering, platform fit (a carousel cannot go to YouTube), duplicates, the time window and your monthly video quota. Surplus variations go out on the following days.
Path
| Field | Type | Description |
|---|---|---|
jobId | UUID required | The cloning job. |
Body: explicit plan
| Field | Type | Description |
|---|---|---|
plan | array see description | 1 to 50 entries of { pieceId, socialAccountId, scheduledAt }. scheduledAt must be between one minute and one year ahead. |
Body: server-planned
| Field | Type | Description |
|---|---|---|
socialAccountIds | array of UUIDs see description | 1 to 20 accounts from publish destinations, in posting order. |
startAt | ISO 8601 timestamp see description | When the first account posts. |
spacingMinutes | integer optional | Minutes between accounts, 0 to 1,440. Default 45. |
jitterMinutes | integer optional | Each time moves by up to this many minutes either way, 0 to 30. Default 10. |
languages | array of strings optional | Which variations to hand out: original and/or language codes. By default originals go first, then translations. |
timeZone | string optional | IANA zone that "one post per account per day" is counted in. Default UTC. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/jobs/b8d2f4a6-1c3e-4b5d-9f7a-2c4e6a8b1d3f/distribute \
-H "Authorization: Bearer $PROLIFIK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: distribute-b8d2f4a6-oct14" \
-d '{
"socialAccountIds": ["8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54", "0e7b3c1d-9f2a-4d8e-b5c6-7a1f2e3d4c5b"],
"startAt": "2026-10-14T09:00:00Z",
"timeZone": "Europe/Vilnius"
}'
Response 200
{
"results": [
{
"pieceId": "d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c",
"socialAccountId": "8d1f5a2e-4b7c-4e19-9a3d-2c6b0f7e1a54",
"scheduledAt": "2026-10-14T09:04:00.000Z",
"status": "scheduled",
"postId": "c2a7e9d4-1f3b-4a8e-b6d0-5e9f2a1c7b38"
}
],
"scheduled": 2,
"failed": 0,
"accounts": 2,
"excluded": []
}
postId is a content publish record. In server-planned mode, excluded lists accounts that could take no variation and why, and accountsWithoutVariation lists accounts left over when variations ran out. An identical entry that already exists comes back with alreadyScheduled: true and is not sent again. Only one distribute per job runs at a time (409 in_progress).
Save a variation to the Library
POST/cloning/pieces/{pieceId}/library
Keeps one variation in the Library, so it can be scheduled or posted from the dashboard like any other piece. Its reviewStatus becomes approved. No body needed.
Path
| Field | Type | Description |
|---|---|---|
pieceId | UUID required | The variation's pieceId. |
Request
curl -X POST https://app.getprolifik.com/api/v1/cloning/pieces/d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c/library \
-H "Authorization: Bearer $PROLIFIK_API_KEY"
Response 200
{ "ok": true, "pieceId": "d4f6a8c2-3e5b-4d7f-8a1c-5e7b9d1f3a4c", "reviewStatus": "approved" }
Returns 409 when the variation has not finished rendering.
OpenAPI document
The machine-readable description of the API is at app.getprolifik.com/api/v1/openapi.json. It is OpenAPI 3.1, needs no key to fetch, and is generated from the same code as the API, so it always lists the live endpoints. Each operation names its scope in x-required-scope. Use it to generate a client or import the API into Postman or Insomnia. Its response schemas are loose in places; where they are, this page shows the real shapes.
curl https://app.getprolifik.com/api/v1/openapi.json -o prolifik-openapi.json
Support
Email info@getprolifik.com with the endpoint, the time of the call and the requestId from the error (the same value as the x-request-id header). For the product itself, start with the Quickstart or the FAQ.
Build on Prolifik
API keys come with every paid plan, from €20 a month, alongside the AI Clipper, auto-posting to four platforms and the MCP server.
See plans and pricing