POST /v1/content/:containerId/publish
Publish a container immediately.
/v1/content/:containerId/publish- Auth
- Bearer
- Scope
- publish:write
Publish now. If you need a pre-publish cancel window, use schedule instead and cancel the scheduled post before it starts publishing.
Nothing publishes until the container's approval flag is set. If the project has requires_approval: true and the container is not yet approved, this call returns 403 APPROVAL_REQUIRED - flipping approval later will still publish, but at that later time, not immediately. Unlike schedule, publish does not stash a pending intent — partners should wait for approval and re-issue. See Approval.
Atomic batch semantics. Up to 50 targets per call. Identical all-or-nothing behavior to schedule — the first invalid socialAccountId aborts the whole call with no rows written.
containerIdstringrequiredCompleted content container id.
Idempotency-Keystring (UUID)optionalSame key + same body replays the cached response. Especially important here - accidental retries double-post.
targetsTarget[]requiredSame shape as schedule. At least one required, max 50. All-or-nothing.
Target shape is identical to /schedule. All three modes (publish, draft, managed) are supported on this endpoint with the same per-target fields.
socialAccountIdstringrequiredAccount id from list-social-accounts. Must already be connected to the same project as the container.modestringrequiredHow the post is delivered. See "Modes" below.One of:publish,draft,managedcaptionOverridestringoptionalReplace the container's caption for this target only. Up to 4000 chars. The publisher reads this in preference to `content_containers.caption`.firstCommentOverridestringoptionalReplace the container's first comment for this target only. Up to 4000 chars.tiktokPostSettingsobjectoptionalTikTok-only knobs. See "TikTok post settings" below. Ignored on `instagram` targets and on `managed` mode.tiktokMusicobjectoptionalTikTok music selection for image / slideshow posts. See "TikTok music" below. Ignored on `instagram` targets. **Rejected with `422 VALIDATION`** when set on a `draft` target (TikTok inbox drafts have no music slot — the creator picks audio on-device). Omit on draft.shareReelToFeedbooleanoptionalInstagram Reels placement (mirrors Meta's Graph API `share_to_feed`). `true` (default) — the Reel appears in the Reels tab AND on the main profile grid. `false` — Reels tab only, skip the grid. **Rejected with `422 VALIDATION`** when set on (a) non-Instagram targets, (b) non-video content containers (slideshow / single-image), or (c) any mode other than `publish`. See "Instagram Reels placement" below.
Modes
| Mode | What it does |
|---|---|
publish | Layers posts to the connected platform automatically. Row lands at published (with externalId + externalUrl populated) on success, or failed (with lastError populated, no retry) on a hard publish failure. |
draft | Pushes the media as a draft to the user's mobile app — TikTok inbox or Instagram SMS. The end-user finishes posting from their phone. On success the polled status flips to draft (terminal from Layers' side — the creator finalizes on-device, invisible to us). On a platform handoff failure the row lands at failed with lastError populated and no retry; the wire enum is invariant — draft always means "delivered to device", failed always means "publisher tried and couldn't deliver." |
managed | Dispatches via the project's connected managed-distribution provider. Requires the social account to belong to a layer with template_id = af058068-ad85-4fb8-92a1-f531b40bfcbc (managed distribution) and the layer's config.provider set. |
IG placement (Reels vs feed) is determined by the container's media_type (image → feed, video → reels, multi → carousel). There is no feed/reels mode on the partner API — the publisher picks the placement automatically.
Instagram requires a Business or Creator account. Instagram Graph API publishing is not available on Personal accounts. A publish target pointed at a Personal-tier IG account lands at failed with lastError carrying the Graph API rejection message. Switch the account to Creator (free) in the Instagram mobile app's Account type and tools settings and re-issue.
externalUrl for Instagram. Instagram permalinks use an opaque shortcode (/p/<shortcode> or /reel/<shortcode>) that can't be derived from the numeric externalId alone. The publisher captures the permalink from the Graph API's ?fields=permalink after publish and surfaces it on externalUrl. A network blip on that follow-up fetch leaves externalUrl: null even though externalId and publishedAt are populated and the post is live — open the IG mobile app for the connected account to view it manually.
TikTok-only fields on Instagram targets. tiktokPostSettings and tiktokMusic are silently ignored on platform: "instagram" targets — they don't map to any Instagram Graph API knob. If you're building a generic publish helper that always sets these fields, expect them to be no-ops for IG; they're not a 422.
draft for Instagram requires the organization's primary operator to have a verified phone number; the SMS draft is sent to that operator. Without a verified phone the publisher's auto-publish loop falls back and the wire status lands at failed.
Instagram Reels placement
Videos posted to Instagram via publish go to Reels by default. Reels are short-form vertical videos and appear in two places on the connected account:
| Where it shows | When |
|---|---|
| Reels tab on the profile | Always — every Reel lands here |
| Main profile grid (with a play-icon overlay) | When shareReelToFeed is omitted or true (Graph API default) |
| Only the Reels tab, not the main grid | When shareReelToFeed: false |
shareReelToFeed maps verbatim to Meta's share_to_feed parameter on the Reels media container.
| Media type | Placement | shareReelToFeed allowed? |
|---|---|---|
| Single video | Reel + grid (default) or Reels tab only (false) | ✅ Yes |
| Slideshow / carousel (multiple images) | Grid only — there is no Reels analog for carousels | ❌ No — 422 VALIDATION |
| Single image | Grid only — there is no Reels analog for single images | ❌ No — 422 VALIDATION |
Slideshow and single-image Instagram posts are grid-only. Setting shareReelToFeed on a non-video container returns 422 VALIDATION (details.reason: "non_video_container"). The Graph API has no share_to_feed analog for CAROUSEL / IMAGE containers — placement is implicit.
TikTok post settings
tiktokPostSettings mirrors the UI's "Advanced settings" panel for TikTok. All fields are optional; missing fields fall back to TikTok's creator_info defaults (typically the most permissive privacy + interactions enabled).
| Field | Type | Notes |
|---|---|---|
privacyLevel | "PUBLIC_TO_EVERYONE" | "MUTUAL_FOLLOW_FRIENDS" | "FOLLOWER_OF_CREATOR" | "SELF_ONLY" | Required by TikTok for direct posts. The publisher defaults to PUBLIC_TO_EVERYONE when omitted. |
disableComment | boolean | Disable comments on the published post. |
disableDuet | boolean | Disable Duet (videos only). |
disableStitch | boolean | Disable Stitch (videos only). |
isBrandOrganic | boolean | "Your Brand" toggle — labels the post as Promotional content. |
isBrandedContent | boolean | "Branded Content" toggle — labels the post as Paid Partnership. Required by TikTok ToS / FTC §255 when a creator is paid by a brand to post. Partners running paid promotions must set this. |
TikTok music
tiktokMusic mirrors the UI's "Background Music" panel for TikTok image / slideshow posts. Applies to publish and managed only — TikTok inbox drafts (draft) cannot stamp music since the creator picks it on-device, and the schema rejects the field on draft targets with 422 VALIDATION (rather than silently dropping it).
| Field | Type | Notes |
|---|---|---|
mode | "none" | "auto" | "manual" | Required. none opts out of music. auto lets TikTok auto-suggest a trending sound. manual uses trackId. |
trackId | string (UUID) | Required when mode: "manual". Track id from GET /v1/tiktok-music. 422 if the id isn't in the catalog. |
"tiktokMusic": { "mode": "auto" }"tiktokMusic": { "mode": "manual", "trackId": "8f1d6c3e-4b2a-4a18-9e4f-c2d7a1b0e999" }manual on publish currently degrades to auto. TikTok's photo-publish API requires a Commercial Music Library id, which the trending-music catalog does not surface — manual and auto produce the same auto_add_music: true payload on direct publish. This matches the Layers UI's behavior. Managed-distribution targets DO honor the manual track: the publisher receives a resolved tiktokMusicLink and uses it verbatim. Use managed mode if you need exact track selection on TikTok image posts.
Two device-handoff paths. mode: "draft" here pushes a draft to the platform-native inbox (TikTok inbox / Instagram SMS draft) immediately. For the UI's "Text me this post" / Elle iMessage flow — sending media + caption + posting instructions to an arbitrary phone number, not to the connected creator account — call POST /v1/content/:containerId/notify-device.
Example request
curl https://api.layers.com/v1/content/cnt_8f1d6c3e-4b2a-4a18-9e4f-c2d7a1b0e999/publish \
-H "Authorization: Bearer lp_..." \
-H "Idempotency-Key: 3a7b2f11-9e4c-4a12-9d8a-b5c7e2f0a111" \
-H "Content-Type: application/json" \
-d '{
"targets": [
{ "socialAccountId": "sa_71b2a4e5-8c3f-4d1a-9e7b-2c5d8f0a1b22", "mode": "publish" }
]
}'const result = await layers.publishing.publishNow(
{
containerId: "cnt_8f1d6c3e-4b2a-4a18-9e4f-c2d7a1b0e999",
targets: [
{ socialAccountId: "sa_71b2a4e5-8c3f-4d1a-9e7b-2c5d8f0a1b22", mode: "publish" },
],
},
{ idempotencyKey: crypto.randomUUID() },
);result = layers.publishing.publish_now(
container_id="cnt_8f1d6c3e-4b2a-4a18-9e4f-c2d7a1b0e999",
targets=[
{"socialAccountId": "sa_71b2a4e5-8c3f-4d1a-9e7b-2c5d8f0a1b22", "mode": "publish"},
],
idempotency_key=str(uuid.uuid4()),
)Response
{
"scheduledPostIds": ["sp_4c8e7d2f-9a1b-4c3d-8e7f-2a1b3c4d5e60"]
}The response shape differs from /schedule — /publish omits both gateStatus and scheduledFor. Pending containers 403 outright (so the only successful state is "queued for immediate publish" and gateStatus would always be "queued"), and scheduledFor would always echo now() — partners that need timestamps poll GET /v1/scheduled-posts/:id and read attemptedAt / publishedAt.
Errors
| Status | Code | When |
|---|---|---|
| 422 | VALIDATION | Empty targets, invalid mode, or schema-level cross-field violations (e.g. tiktokMusic set on a draft target, or shareReelToFeed set on a non-Instagram target / non-video container / non-publish mode — see callouts above). When raised by shareReelToFeed, details.reason is one of "non_instagram_target" (also includes details.platform and details.socialAccountId) or "non_video_container" (also includes details.mediaType). |
| 422 | SOCIAL_ACCOUNT_NOT_PUBLISH_READY | A target's social account is connected to the project but isn't yet provisioned to publish in the requested mode. details.socialAccountId and details.mode identify the offending target. Verify the social account's setup for this project before retrying. |
| 403 | APPROVAL_REQUIRED | Container not approved and project still in the approval window. Publish does not stash intent — re-issue once approved. |
| 404 | NOT_FOUND | Container or any target's account not in your organization (all-or-nothing). |
| 409 | CONFLICT | Container not completed. |
| 409 | CONFLICT_DUPLICATE_ID | A partner-supplied scheduledPostId already exists. Pick a fresh sp_<uuid> and retry — same id can't be reused across calls (DB unique constraint, not a replay key; pair with Idempotency-Key if you need request-level replay). |
| 409 | CONTENT_REJECTED | Container's approval_status is rejected. Regenerate or replace the container. |
See also
POST /v1/content/:id/schedule- queue for laterGET /v1/scheduled-posts/:id- watch it publishGET /v1/projects/:id/scheduled-posts- list posts in a projectDELETE /v1/scheduled-posts/:id- cancel before it publishes