Docs
Data

Posts

Stored LinkedIn and X post performance, and the Publishing Community queue for public comments on owned posts.

Posts published through OXYGEN's LinkedIn and X Publishing paths are stored as Posts data, and recent posts created directly on a connected LinkedIn account are discovered into the same store. The latest cumulative metric snapshots back Analytics → Content; public comments on recent owned LinkedIn posts back the durable, oldest-first Community queue at /publishing/comments. It has no sidebar entry: open it by URL or as Publishing · Community in ⌘K.

Three things that look alike and are not: a public comment and its thread belong to Posts; a private DM is a Message in the Unibox; draft text, assignment, and notes attached to a public comment are local workspace state that saving never posts.

Creator-program enrollment

A creator's program post remains one canonical personal Post. The creator chooses Personal or one joined Program on the first save; a Program choice adds a versioned enrollment and always starts unapproved. Before publication begins, the creator can remove that enrollment without deleting the personal draft. The program keeps its frozen shared snapshot and retained public history, while later personal edits remain private.

Removal and re-enrollment create distinct enrollment versions. Read the current enrollment_id, enrollment_version, and source_revision with oxygen ugc posts list; send the version on each UGC edit, creator approval, and posts history read. Brands never approve creator posts. An old reviewed action cannot approve a newer enrollment. Joining, sponsorship and delegated publishing approval do not opt the creator into peer engagement: the host must permit it, and the creator separately controls their sender, voice, actions and wallet-funded caps.

Analytics

oxygen publishing analytics summary --range 30d --json
oxygen publishing analytics summary --channel x --range 30d --json
oxygen publishing analytics post <post-id> --json
oxygen publishing analytics timeseries --range 30d --tz Europe/Berlin --json
oxygen publishing analytics account --provider linkedin --json
oxygen publishing analytics emv --cpm-json '{"linkedin":30,"x":12}' --json

The summary defaults to owned LinkedIn posts recorded in OXYGEN, including recently discovered posts created directly on LinkedIn. --channel x instead covers X posts published through OXYGEN. Both paths read stored snapshots and never call the selected provider when opened or queried. snapshotted_posts counts posts with a stored background sync pass; its legacy alias measured_posts does not mean every engagement value was populated. Use coverage.<metric>.measured_posts to see how many posts reported a non-null value for each metric. A null is unknown or unavailable, never zero. Stale and pending snapshots retry automatically; there is no manual provider retry to run.

The current LinkedIn rail supplies reactions, comments, and reposts, plus the author's own impressions when LinkedIn returns them; it has no saves, sends, clicks, or video views. X ordinary post lookup maps impressions, likes, replies, reposts, bookmarks, conditional link clicks, and conditional video views into the same canonical fields; it exposes no send counter. Your reposts of other people's posts are counted in reposts_excluded and left out of every total, because a repost's reactions, comments, and impressions belong to the original author's post.

timeseries reports impressions and engagements earned per day across every tracked post, including posts published before the range, with reactions, comments, and reposts on their own and whether each is growing or shrinking against the previous period (trend.basis names the comparison). It also reports each LinkedIn account's follower change. A day with no tracked post is null, never zero, and a gain first read after a gap of up to three days is spread across it and marked estimated. --tz sets the day boundaries; the default is UTC.

account returns the daily follower series of each connected LinkedIn and X account (--provider picks one network). Followers are read once a day in the background; each account reports how fresh its last read is, and a stale value stays available. post returns one post's daily series and totals, its growth curve, its 24-hour and 7-day benchmarks against your workspace median, and its earned media value.

Earned media value is impressions ÷ 1,000 × the CPM you set for that channel, in USD; set it with emv --cpm-json. It stays null, with the reason, until a CPM is set and on any post whose impressions the provider did not return. --account narrows summary, timeseries, and account to chosen profiles: pass an account key from the summary's accounts list, or a LinkedIn sender id from oxygen senders list, and repeat it for several. Every analytics read uses stored data only: no provider call, 0 credits.

In the app, Analytics → Content draws the same reads: what each day earned, a Followers card per network, and Top posts. Once more than one profile has posted in the range, a profile filter narrows the page the way --account does. A published post's page in Publishing (/publishing/posts/<id>) shows its Performance underneath: impressions and engagements, how they grew after publishing, earned media value, and when it was last read. Over MCP, oxygen_publishing_analytics covers the same reads with scope set to summary, timeseries, post, or account, and set_cpm saves the CPM.

Publishing Community

oxygen publishing comments list --view unanswered --json
oxygen publishing comments list --view all --json
oxygen publishing comments get <comment-id> --json

unanswered is an operating queue, not one database state: it spans needs_reply, draft, awaiting_approval, and action_unavailable. all adds replied, resolved, ignored, and spam, still bounded to the rolling 30-day recent-owned-post scope — not lifetime LinkedIn history. For one exact state pass --status <state> instead; --view and --status cannot be combined.

Opening this queue is a local tenant-database read. It uses 0 OXYGEN credits and makes no provider call. A background worker discovers and polls recent owned posts with a six-hour freshness target.

Every list response includes empty_queue_is_current beside the count and a freshness block:

FieldMeaning
statuscurrent, awaiting_first_sync, partial, stale, backing_off, or no_targets
sync_target_postsRecent owned posts the worker should poll
completed_postsPosts that have completed at least one pass
current_postsPosts with a successful pass inside the six-hour target
failed_postsPosts whose latest pass failed and is waiting to retry
last_completed_atLatest successful pass, or null before the first success
next_sync_atNext scheduled attempt or retry
last_errorExact provider/quota failure when polling is backing off
counts_trustworthytrue only when discovery and comment polling are current
automatic_retry_atScheduled automatic retry while backing off
manual_retry_availableAlways false; the worker owns quota-safe retry timing
guidanceSelf-contained degraded-state instruction, or null when none is needed

If an empty response has empty_queue_is_current: false, do not conclude that LinkedIn has no comments. freshness.counts_trustworthy remains false and freshness.guidance names the automatic retry time and exact failure. There is deliberately no manual sync button or retry command: the worker retries automatically, while a user-triggered retry loop could consume the same LinkedIn account quota that caused the backoff.

A reply written directly on LinkedIn is reconciled by the next successful provider sync. OXYGEN recognizes the connected account as the author and moves only that reply's exact parent comment to replied; local drafts, notes, and reply receipts remain available for inspection. Creating or approving a reply through OXYGEN is refused if the bound LinkedIn sender is no longer active.

Local queue versus live provider reads

oxygen posts get|comments|reactions are a separate low-level path for one known LinkedIn post. Each is a live provider read. It uses 0 OXYGEN credits but consumes the connected sender's metered account-read allowance, and it does not populate or refresh the durable queue. Do not use that path when a task forbids provider calls.

Safe public reply flow

oxygen publishing comments update <comment-id> --draft "Exact reply" --json
oxygen publishing comments reply <comment-id> --text "Exact reply" --json
oxygen publishing comments approve <action-id> --content-hash <sha256> --approved --json

update saves only local workspace state. reply freezes an exact unsent preview and returns its immutable action id plus SHA-256 content hash; it makes no provider call. Only approve performs the public LinkedIn write, and only for the newest preview with the exact hash. Never retry an effect_unknown action automatically—open the returned comment deep-link and verify the public thread first.

CLI, MCP, and the web workspace run the same contract. Over MCP, inspect oxygen_publishing_comments with oxygen_capabilities_schema, then use oxygen_capabilities_call with its selected operation; this tool combines reads and approval-gated replies. It runs from core, or is listed directly by ?toolset=publishing. LinkedIn is the only channel in Community today.

  • Approvals — the external-write gate.
  • CLI and MCP — the two execution surfaces.
  • Sequences — private outreach after a commenter becomes a warm lead.

On this page