Sign inStart creating

Publishing Hub

Implementation: src/server/services/publishing.ts. UI: /studio/publishing, project → Publish.

Credential vault

Never store raw passwords. Only platform-issued tokens.

Tokens are sealed with AES-256-GCM under TOKEN_VAULT_KEY (sealToken / openToken). connectAccount refuses to store anything if no key is configured — failing loudly beats storing plaintext.

vaultStatus() reports which mode is active, and the UI shows it:

ModeMeaning
kmsKMS_KEY_ID set — envelope encryption with rotation. Production target.
local_keyReal AES-256-GCM at rest, but one local key. Not the Part 8.2 scheme.
unconfiguredConnecting an account is refused.

Calling local_key production-grade would be the kind of small lie that becomes a breach post-mortem.

Disconnect destroys the token row rather than soft-deleting it.

Platform reality

Each adapter declares readiness and an apiReality string, both shown in the UI before a creator queues a post.

PlatformReadinessReality
YouTubeoauth_readyData API v3 — mature, quota-limited, audit for higher quota. Build first.
TikTokrequires_auditContent Posting API needs an audited developer app. Ship inbox-draft first, direct-post after audit.
Instagramrequires_app_reviewGraph API — Business/Creator accounts only. Personal accounts cannot publish via API.
Facebookrequires_app_reviewPages well supported. Groups API heavily restricted post-2024 → share-dialog fallback.
Xpaid_tierv2 posting requires a paid tier. Chunked media upload. Budget for fees.
Rumbleassisted_onlyNo public posting API. Prepare metadata + open the upload page.

Rumble's assisted flow is a design decision, not an omission, and the UI says so. Setting expectations before someone queues twenty posts is cheaper than apologising after.

Auto-format engine

planFormat produces a per-destination plan:

  • Crop — 9:16 uses subject_track, not center-crop. A static center-crop destroys wide shots, which is most of a film.
  • Trim — with an explicit note when the source exceeds the platform cap.
  • Safe zones — real per-aspect values (9:16 loses the bottom 20% to caption + CTA and the top 12% to the profile row).
  • Captions — burned for vertical, sidecar for horizontal.
  • Copy — platform-tuned title/description/hashtags, editable before posting.
  • AI disclosure — always on. Where a platform exposes an API flag, it is set; where not (X, Rumble), disclosure goes in the description and the plan says which.

Publish queue

queuePost runs Stage 0 screening first — everything leaving the platform is screened even when it skips Theater review, because under each platform's API terms you are the publisher of record.

Org-shared connections set requiresOwnerApproval, so posts land in needs_approval.

executePost: Rumble returns an assisted-flow payload; every other platform requires configured credentials and returns a specific, actionable error naming the file to implement. Retries use exponential backoff capped at six hours.

Campaign planner

planCampaign produces the reviewable "MARKET THIS FILM" plan — trailer set, posters, platform-native clips, captions, and a drip schedule (anchor Monday, clip Wednesday, premiere Friday) with a total credit cost. Nothing posts until approved.

Export

Five delivery formats plus EDL, CMX3600 and SRT. Every export carries a C2PA manifest and AI-generated labelling.

Manifests are unsigned without C2PA_SIGNING_CERT, and exportManifest returns a note saying downstream verifiers will show "no valid claim signature". An unsigned manifest presented as verified provenance is worse than none.