Publishing posts
Compose, schedule, and approve a post so Oxygen publishes it on time — and nothing goes out that a human did not approve.
Publishing holds scheduled posts, gates them behind a human approval, dispatches them to LinkedIn, X, Instagram, TikTok, Facebook, and YouTube, and records every attempt. Performance and public comments on what shipped belong to Posts.
Only an approved post publishes. A publish time is not permission — an unapproved post sits in the queue past its scheduled minute. Every content edit revokes approval, because approval binds the exact copy reviewed: change a word, approve again.
/publishing route sits behind the OXYGEN_PUBLISHING_ENABLED switch. When it is off the routes 404 and the Publishing sidebar item is gone — there is no setting you can flip yourself.Compose
In the sidebar: Publishing (/publishing) → New post (/publishing/posts/new).
Click a chip in the channel row to add that channel as a target and focus its composer; each target keeps its own body and its own limit (LinkedIn 3,000 characters, Instagram 2,200, TikTok 2,200, YouTube Shorts 100, X 280 — or 25,000 when the selected X account is Premium). Add a first comment posts a comment automatically right after the post — every composer channel except TikTok. The right rail holds the schedule picker, a comma-separated Tags field, and Schedule / Post Now.
Both buttons approve as they save: you just read the post. On an existing post the composer flushes the pending autosave first, so approval binds what is on your screen.
The CLI creates a post unapproved unless you pass --approved:
oxygen publishing posts create --publish-at 2026-09-01T09:00:00Z \
--sender <sender-account-id> --text "Copy. Mention @<public-identifier>." --json--sender is the LinkedIn sender; other channels take --provider and --provider-connection. Facebook has no composer chip — it is CLI-only. Attach media with oxygen publishing media upload-url, PUT the bytes, then media uploaded. Retag with oxygen publishing tags set <post-id> --add launch.
Approve
oxygen publishing posts list --approval-status needs_approval --json
oxygen publishing posts approve <post-id> --jsonApprove runs a deterministic channel lint, recomputed on every read and never stored. Five findings are errors and block approval: an empty body, a LinkedIn body over 3,000 characters, an Instagram caption over 2,200, a PDF on a channel that cannot carry one, and a poll outside 2–4 options. Everything else — including an over-limit X body, whose tier can only be read from a cache — is a warning that publishes anyway. On LinkedIn, approval also resolves every inline @<public-identifier> and blocks with inspectable details if one cannot be verified. In the web queue this is Approve… in a row's ⋯ menu.
A workflow can create and schedule posts but never approve one: create forces needs_approval and strips any approval field a recipe passes. Cron cannot publish for you.
The queue
/publishing lists every post, the ones waiting on you hoisted to the top and badged Waiting on approval. The icon strip beside New post switches between List, Board, and Calendar; tag chips filter the list. A row's ⋯ menu carries Edit post, Approve…, Retry now, Reopen, Cancel…, and Delete…, whichever the state allows. /publishing/posts/<id> shows the attempt history, with Retry on a failed post and Cancel on anything pending.
| Situation | Do this |
|---|---|
| Provider rejected it | oxygen publishing posts retry <post-id> |
| Stop it before it sends | oxygen publishing posts cancel <post-id> |
| Undo that cancel | oxygen publishing posts uncancel <post-id> — reopens unapproved |
| Roll the text back | oxygen publishing posts revisions <post-id>, then restore --revision <n> |
| Remove it entirely | oxygen publishing posts delete <post-id> — refused once attempted; cancel instead, so the attempt ledger stays honest |
Each sender is paced at 25 published posts per rolling 24 hours — a rolling window, not a calendar day. A post over that ceiling is deferred rather than failed, but deferral is not unbounded: once it has been dragged more than 24 hours past its original publish_at, it fails loudly instead of publishing day-old content later. oxygen publishing posts note <post-id> --text "..." leaves an internal note, never published.
Drafts, ideas, import
/publishing/drafts holds the backlog upstream of the queue — AI variants and parked ideas — each with Accept, Edit, and Reject. Accepting creates a post that still needs approval. Generating copy is deliberately not a button there, because it spends credits.
--max-credits is the ceiling the call cannot cross.oxygen publishing posts draft --template feature_launch --topic "..." --max-credits 20 --json
oxygen publishing posts review <post-id> --max-credits 10 --jsonreview is an advisory voice-and-claims check that never blocks approval. oxygen publishing ideas add --text "..." parks a free idea; oxygen publishing import --file posts.csv is dry-run by default, and --approved writes the rows as needs-approval drafts.
Amplification
/publishing/amplification lists standing auto-engagement policies — named teammates' real LinkedIn accounts reacting to and commenting on posts in scope — with per-policy Arm / Disarm and the action ledger. There is deliberately no create button: that permission has to be granted with its caps stated out loud, which only the CLI/MCP makes you do.
oxygen publishing amplification create --name "Launch week" --scope tag --tag launch \
--senders <id1>,<id2> --actions reaction --max-credits 50 --approved --json
oxygen publishing amplification enable <policy-id> --approved --jsonA policy is created disabled; enable --approved arms it. Both spend credits and write publicly. disable is never gated and skips every action still planned.
No publishing tool is in the default MCP set — connect with ?toolset=publishing.
Related
- Post analytics and Community — how a published post performed, and its public comments.
- Approvals — the external-write gate everywhere else.