Layers
Partner APIAPI referenceContent

POST /v1/projects/:projectId/content

Start a content generation job. The server picks the format unless you pin one.

View as Markdown
POST/v1/projects/:projectId/content
Phase 1stableidempotent
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

  • projectId
    string (uuid)required
    The project to generate content for.

Body

Body
  • id
    string (uuid)optional
    Your id for the first container. Enables idempotent creation.
  • projectLayerId
    string (uuid)optional
    Which content layer to run through. Omit if the project only has one.
  • format
    stringoptionaldefault: "auto"
    Content shape.
    One of: video_remix, slideshow_remix, ugc_remix, auto
  • variantCount
    integeroptionaldefault: 1
    Number of container variants to generate. Max 5.
  • brief
    objectoptional
    Creative brief. See rows below.
  • brief.topic
    stringoptional
    Subject or premise.
  • brief.angle
    stringoptional
    Point of view.
  • brief.cta
    stringoptional
    Closing call-to-action.
  • brief.valuePropositions
    string[]optional
    Up to 20 benefit bullets.
  • brief.hook
    stringoptional
    Opening line or premise.
  • brief.targetPlatforms
    string[]optional
    Where this content will ship.
    One of: instagram, tiktok, youtube
  • brief.influencerId
    string (uuid)optional
    Narrate with this influencer.
  • brief.themeTags
    string[]optional
    Themes for the generator to lean on.
  • brief.referenceMediaIds
    string[]optional
    Uploaded media the generator can remix from.
  • brief.language
    stringoptional
    BCP-47 tag.
  • references
    objectoptional
    Structured references for the generator.
  • references.sourcePostIds
    string[]optional
    Platform posts to seed from (max 20).
  • references.assetIds
    string[]optional
    Media library asset ids (max 20).
  • targetPlatforms
    string[]optional
    Override brief-level targets.
    One of: tiktok, instagram, youtube
  • budget
    objectoptional
    Credit cap for this request. `{ maxCredits: integer }`.
  • metadata
    objectoptional
    Arbitrary partner-supplied metadata, stored on the container.

Request

terminal
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"
    }
  }'
generate-content.ts
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();
generate_content.py
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

202Job accepted. Containers exist in queued/generating status — poll the job.
{
  "jobId": "job_01HXZ9G7KMV2QX8Y1S5RJW3B7T",
  "kind": "content_generate",
  "status": "running",
  "containerIds": ["cnt_01HXZ9...", "cnt_01HXZA..."],
  "format": "video_remix",
  "locationUrl": "/v1/jobs/job_01HXZ9G7KMV2QX8Y1S5RJW3B7T"
}
402Project credits below the generation cost.
{
  "error": {
    "code": "BILLING_EXHAUSTED",
    "message": "Insufficient content-generation credits.",
    "requestId": "req_...",
    "details": { "required": 40, "available": 12 }
  }
}
409Project has multiple content layers and projectLayerId wasn't passed.
{
  "error": {
    "code": "CONFLICT",
    "message": "Multiple content layers on this project. Pass projectLayerId.",
    "requestId": "req_...",
    "details": { "candidates": ["lyr_01HX...", "lyr_02HX..."] }
  }
}

Stages

Job stages (order guaranteed)
  • planning
    stageoptional
    Compose the shot list, caption drafts, influencer narrative.
  • generating_visuals
    stageoptional
    Render or remix media. This is the longest stage — 30s–3min depending on format.
  • assembling
    stageoptional
    Stitch media, overlay captions, mix audio.
  • finalizing
    stageoptional
    Persist container; surface thumbnails; write assets to object storage.

Notes

Approval gating is enforced at schedule-time, not here. If the project has requiresApproval: true, generation still runs — the container lands in approvalStatus: "pending" and schedule refuses it until you call approve.

  • 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.
  • variantCount is per-request. One jobId covers the lot; there's no fan-out across multiple jobs.

Errors

CodeWhen
VALIDATION_FAILEDMissing brief, bad enum, variantCount > 5.
CONFLICTMultiple content layers and no projectLayerId; or id collision with different brief.
BILLING_EXHAUSTEDCredits insufficient.
MODERATION_BLOCKEDBrief failed safety.
NOT_FOUNDprojectLayerId or influencerId not in this project.

See also

On this page