Composer
Draft one post, target multiple connected accounts, preview each channel against its real character and media limits, and rewrite the draft with your own Anthropic key — then save, schedule, or publish.
Composer
The composer is where a single idea becomes a post. You write the body once,
pick which connected accounts should receive it, watch a live per-channel
preview and character counter, optionally have AI tighten the copy, then save it
as a draft, schedule it, or publish immediately. The page is wired to the live
/v1/social API — it lists your accounts from GET /v1/social/accounts and
creates posts through POST /v1/social/posts.
Live surface
Account selection, per-channel limit enforcement, draft/schedule/publish, and
AI rewriting are all backed by real endpoints. The limits table is currently a
client-side map (web/src/app/(app)/social/content-limits.ts) pending a
content_limits field on the channel framework.
Per-channel limits
Because a 3,000-character LinkedIn post won't fit in a 280-character tweet, the
composer shows a side-by-side preview per selected channel with a live counter
and an over-limit warning. The current limits (from content-limits.ts):
| Provider | Char limit | Media |
|---|---|---|
| 3,000 | 9 images / 1 video | |
| 63,206 | 10 images / 1 video | |
| 2,200 | 10-image carousel | |
| threads | 500 | 10 images / 1 video |
| 280 | 4 images / 1 video / 1 GIF | |
| tiktok | 2,200 | 1 video |
| youtube | 5,000 | 1 video |
| bluesky | 300 | 4 images |
| mastodon | 500 | 4 images / 1 video / 1 audio |
Limits are a versioned client-side map
These figures reflect each network's public policy as of the map's last update.
When the channel framework exposes a canonical content_limits field, the
composer will read it live and this map is removed. Treat an over-limit warning
as advisory — the provider is the final authority at publish time.
AI rewrite
The composer's rewrite actions call POST /v1/social/composer/ai-improve, which
routes through the tenant's own Anthropic provider (BYOK). Modes
(_MODE_INSTRUCTIONS in api/routes/social.py):
improve— tighten grammar and phrasing, keep the author's voice, length within ~±15%.shorter— cut roughly 40%, drop filler, keep every concrete fact.punchier— sharpen the hook and energy without inventing claims.hashtags— leave the body unchanged, append 3–5 relevant hashtags.first_line— rewrite only the opening sentence, keep every other line verbatim.
curl -X POST https://api.pact.place/v1/social/composer/ai-improve \
-H "Authorization: Bearer $PACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"shorter","content":"Our Q3 release ships today...","provider":"linkedin"}'
If the tenant has no Anthropic key configured, the endpoint returns 409 ai_not_configured so the UI can render a "Connect AI provider" call to action
rather than a generic error. The assistant is deliberately conservative: a
contentless draft (a lone emoji, random characters) is returned unchanged rather
than padded into a fabricated post, and the draft is fenced as data so text like
"ignore your instructions" is treated as post copy, not a command.
Saving, scheduling, publishing
- 1
Pick accounts
Select one or more connected accounts. A disabled account rejects the post with409 account_disabled. - 2
Write and preview
Type the body; each selected channel renders a preview with its own counter. - 3
Choose an action
Save as draft, set ascheduled_fortime, or setpublish_now: true. Scheduling and immediate publish are mutually exclusive (the API returns400if both are sent). - 4
Publish
Publish immediately, or later viaPOST /v1/social/posts/{public_id}/publish. A provider error flips the post tofailedwith afailure_reason.
A scheduled post does not send itself yet
Setting scheduled_for records the intent and places the post on the
calendar, but no worker currently publishes scheduled
posts automatically at their due time. Publish scheduled posts explicitly until
that background job ships.