POST /v1/projects/:projectId/content
Start a content generation job. The server picks the format unless you pin one.
/v1/projects/:projectId/content- Auth
- Bearer
- Scope
- content:write
Starts a content-generate job. Returns 202 with one or
more containerIds and a single jobId that covers the whole request.
This is the main content entry point. Pass a brief and we resolve the right content layer on the project, pick a format (video remix, slideshow remix, UGC remix, or auto), and run the appropriate generation workflow. One call produces variantCount containers — each one a full generated post with hook, caption, and media.
Pin format when you want a specific shape. Leave it on "auto" (the default) and we pick based on the project's brand context, the influencer, and what's been performing.
Resolving the content layer
If the project has exactly one content layer, we use it. If it has multiple (e.g. Social Content and Managed UGC), pass projectLayerId to disambiguate — otherwise the request returns 409 CONFLICT with the candidate layers.
Idempotency
Pass id to make the request idempotent — we treat it as the first container's id and UPSERT. Replaying with the same id returns the prior response; replaying with a different brief returns 409 CONFLICT. Pair it with Idempotency-Key for a 24-hour replay window.
Path parameters
projectIdstring (uuid)requiredThe project to generate content for.
Body
idstring (uuid)optionalYour id for the first container. Enables idempotent creation.projectLayerIdstring (uuid)optionalWhich content layer to run through. Omit if the project only has one.formatstringoptionaldefault: "auto"Content shape.One of:video_remix,slideshow_remix,ugc_remix,autovariantCountintegeroptionaldefault: 1Number of container variants to generate. Max 5.briefobjectoptionalCreative brief. See rows below.brief.topicstringoptionalSubject or premise.brief.anglestringoptionalPoint of view.brief.ctastringoptionalClosing call-to-action.brief.valuePropositionsstring[]optionalUp to 20 benefit bullets.brief.hookstringoptionalOpening line or premise.brief.targetPlatformsstring[]optionalWhere this content will ship.One of:instagram,tiktok,youtubebrief.influencerIdstring (uuid)optionalNarrate with this influencer.brief.themeTagsstring[]optionalThemes for the generator to lean on.brief.referenceMediaIdsstring[]optionalUploaded media the generator can remix from.brief.languagestringoptionalBCP-47 tag.referencesobjectoptionalStructured references for the generator.references.sourcePostIdsstring[]optionalPlatform posts to seed from (max 20).references.assetIdsstring[]optionalMedia library asset ids (max 20).targetPlatformsstring[]optionalOverride brief-level targets.One of:tiktok,instagram,youtubebudgetobjectoptionalCredit cap for this request. `{ maxCredits: integer }`.metadataobjectoptionalArbitrary partner-supplied metadata, stored on the container.
Request
curl -X POST https://api.layers.com/v1/projects/{projectId}/content \
-H "X-Api-Key: $LAYERS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8d2f1a3e-0b4c-4a11-9f7e-33c0a2c1bd55" \
-d '{
"format": "video_remix",
"variantCount": 2,
"brief": {
"hook": "Your first 30 days of running, one clip at a time.",
"targetPlatforms": ["instagram", "tiktok"],
"influencerId": "inf_01HXZ9...",
"themeTags": ["beginner-friendly", "consistency"],
"language": "en"
}
}'const res = await fetch(
`https://api.layers.com/v1/projects/${projectId}/content`,
{
method: 'POST',
headers: {
'X-Api-Key': process.env.LAYERS_API_KEY!,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
variantCount: 3,
brief: {
targetPlatforms: ['instagram'],
influencerId,
themeTags: ['habit-tracking'],
},
}),
},
);
const { jobId, containerIds } = await res.json();import os, uuid, httpx
r = httpx.post(
f"https://api.layers.com/v1/projects/{project_id}/content",
headers={
"X-Api-Key": os.environ["LAYERS_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"format": "auto",
"variantCount": 2,
"brief": {
"targetPlatforms": ["tiktok"],
"influencerId": influencer_id,
"themeTags": ["fitness"],
},
},
)
job = r.json()Responses
{
"jobId": "job_01HXZ9G7KMV2QX8Y1S5RJW3B7T",
"kind": "content_generate",
"status": "running",
"containerIds": ["cnt_01HXZ9...", "cnt_01HXZA..."],
"format": "video_remix",
"locationUrl": "/v1/jobs/job_01HXZ9G7KMV2QX8Y1S5RJW3B7T"
}{
"error": {
"code": "BILLING_EXHAUSTED",
"message": "Insufficient content-generation credits.",
"requestId": "req_...",
"details": { "required": 40, "available": 12 }
}
}{
"error": {
"code": "CONFLICT",
"message": "Multiple content layers on this project. Pass projectLayerId.",
"requestId": "req_...",
"details": { "candidates": ["lyr_01HX...", "lyr_02HX..."] }
}
}Stages
planningstageoptionalCompose the shot list, caption drafts, influencer narrative.generating_visualsstageoptionalRender or remix media. This is the longest stage — 30s–3min depending on format.assemblingstageoptionalStitch media, overlay captions, mix audio.finalizingstageoptionalPersist container; surface thumbnails; write assets to object storage.
Notes
format: "auto"is the sane default. Only pin a format if you're A/B-testing shape, or the customer asked for a specific one. Our auto-pick uses what's been working for this project.variantCountis per-request. OnejobIdcovers the lot; there's no fan-out across multiple jobs.
Errors
| Code | When |
|---|---|
VALIDATION_FAILED | Missing brief, bad enum, variantCount > 5. |
CONFLICT | Multiple content layers and no projectLayerId; or id collision with different brief. |
BILLING_EXHAUSTED | Credits insufficient. |
MODERATION_BLOCKED | Brief failed safety. |
NOT_FOUND | projectLayerId or influencerId not in this project. |