Appearance
Publishing a video
The quickstart ran the whole loop in one pass. This page is the model underneath it: assets, publications, targets, and the states they travel.
Connections come first
You can only publish to a destination you've connected. A connection is a platform account you've authorized socialized.dev to publish to: a YouTube channel, an Instagram account, a Facebook page. A brand holds at most one connection per platform.
Connecting uses OAuth, so it happens in a browser, which is a dashboard step. Connection management, including listing active connections or removing them, is done exclusively in the dashboard and is not available via API keys.
Assets: upload once
The source video is an asset, stored once. Assets are account-scoped, meaning a video uploaded once can be published under any brand in the account.
NOTE
If you are using the socialized.dev CLI (socialized.dev publications create), the CLI automatically handles the multipart asset upload ceremony internally for local file paths, making it a single step.
For direct API integrations, uploads are presigned and multipart: you upload each part directly to a URL the API returns:
POST /assetswithfilename,contentType, andsizeBytes: returns a plan with one presignedPUTURL per part (parts are 10 MiB).PUTeach part directly to its URL; keep eachETag. Part URLs are signed forhostonly, so no extra headers are required — a plainPUTwith a normalContent-Length(no chunked transfer encoding) is enough. AContent-Typeon a part is ignored: the asset's type is thecontentTypefrom step 1.POST /assets/{uploadId}/completewith the{ partNumber, etag }[](ETags may be quoted or unquoted). The asset is recorded and becomesready.
A cover is uploaded as its own image asset (POST /assets with an image contentType) and referenced by the publication via coverAssetId. Keep it under 2 MB — YouTube's thumbnail API refuses anything larger, and a YouTube target with an over-2MB cover fails validation at publish time rather than posting without its thumbnail. A JPEG at 1080p is typically well under the cap.
An asset only ever reaches ready: there is no half-uploaded state to publish by accident.
Upload, then publish within 48 hours. An asset that isn't attached to a publication — as the video or the cover — within 48 hours of upload is reclaimed automatically: both the stored file and its record are deleted. The clock runs from when you upload, not from last use, so finish the publish inside that window. Once an asset is attached to a publication it persists.
Publications & targets
A publication is one "post once": a title, a description, an optional cover, and a list of targets. Each target is one destination.
sh
curl -s -X POST "$API/brands/$BRAND/publications" \
-H "Authorization: Bearer $SK" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"videoAssetId": "asset_video_…",
"coverAssetId": "asset_cover_…",
"title": "Launch clip",
"description": "…",
"targets": [
{ "platform": "youtube" },
{ "platform": "instagram" }
]
}'Each target carries a per-platform options object (it defaults to {}). Up to 10 targets per call. An override wins over the publication's base title / description; an absent field falls back to the base.
YouTube options:
| Field | Type | Limit / values | Default | Fallback when absent |
|---|---|---|---|---|
title | string | 1–100 chars | — | base title |
description | string | max 5000 chars | — | base description |
tags | string[] | 500 chars total (the joining commas count) | — | none sent |
categoryId | string | — | — | none sent |
privacyStatus | enum | public | unlisted | private | public | — |
madeForKids | boolean | — | false | — |
Instagram options:
| Field | Type | Limit | Fallback when absent |
|---|---|---|---|
caption | string | max 2200 chars | base description, then base title if it is empty |
Facebook options:
| Field | Type | Limit | Fallback when absent |
|---|---|---|---|
title | string | 1–250 chars | base title |
description | string | max 4000 chars | base description, then base title if it is empty |
format | 'auto' | 'reel' | 'video' | — | 'auto' |
Reels vs Page videos. Facebook has two publish surfaces. Reels go through /{page-id}/video_reels and must sit in the 3–180s window (Meta raised the old 90s cap to ~3 min mid-2025). Normal Page videos go through /{page-id}/videos, which accepts much longer clips (~4 GB / ~240 min by third-party consensus; the authoritative per-Page answer is GET /{page-id}?fields=video_upload_limits). With format: 'auto' (the default), a clip that fits the Reels window is published as a Reel; everything else — including an unknown duration — lands as a Page video, so a long clip is never rejected when a safe endpoint exists. Explicit reel fails validation outside that window. v1's /videos path is hosted-URL only (file_url body param); there is no byte-push fallback.
TikTok has no connector yet, so its options stay a free-form bag.
The API validates the effective message (the override, or the base it falls back to) against each platform's limits. A field that overflows fails the request with a 422 platform_message_too_long error carrying { platform, field, limit } — this catches a base message too long for a platform even when no override is set (for example a 150-character base title fanned out to YouTube's 100-character cap).
The API validates before it accepts: the video and cover assets must be ready. You do not specify a connection ID; the server automatically resolves the brand's single active connection for each platform. If a platform has no active connection, the request fails with a 422 platform_not_connected error. If you specify the same platform twice in one call, the request fails with a 422 duplicate_platform error.
The lifecycle
Once accepted, the publication runs on its own: each target is sent, transient failures are retried, and every target settles to a final state you can read back. Each target travels:
| Target status | Meaning |
|---|---|
pending | accepted, waiting to send |
processing | being sent to the platform |
published | live; externalUrl is the post |
failed | the platform rejected it; error says why |
deleted | the live post was deleted on its platform through the API (see below) |
The publication aggregates its targets:
| Publication status | Meaning |
|---|---|
queued | accepted, nothing sent yet |
publishing | at least one target is in flight |
done | every target published |
partial | some published, some failed |
failed | every target failed |
Read it back with GET /brands/{brandId}/publications/{id} (the full record, with every target), or list the brand's publications with GET /brands/{brandId}/publications. Or skip polling entirely and subscribe to a webhook.
Add destinations, retry failures
A publication is not frozen once sent:
PATCH /brands/{brandId}/publications/{id}: edit the publication details (such astitle,description,coverAssetId, orscheduledAt) and optionally add new targets. Already-live posts are left untouched.POST /brands/{brandId}/publications/{id}/targets: an append-only endpoint to send the video to more destinations without editing the message. Already-live posts are left untouched.POST /brands/{brandId}/publications/{id}/targets/{targetId}/retry: re-queue afailedtarget. Only failed targets are retryable: any other status returns a409conflict.
Writes to a removed publication (see below) return 409 publication_removed — a removed record is readable but frozen.
Deleting
Deletion is two separate operations — one for your records, one for the live posts — because platform support is uneven:
DELETE /brands/{brandId}/publications/{id}removes the publication from socialized.dev only: it leaves your list (reads by id keep working, witharchivedAtset), and every post already live on a platform stays live. Returns409 publication_in_flightwhile targets are still publishing.DELETE /brands/{brandId}/publications/{id}/targets/{targetId}/postdeletes the target's live post on its platform. Supported for YouTube and Facebook; Instagram's API cannot delete a published reel (422 platform_delete_unsupported). The target settles todeleted(external ids are kept for your records), apublication.target.deletedwebhook fires, and the call is idempotent — deleting an already-deletedtarget, or a post that was already removed on the platform, succeeds.