# Publish your first post

Publish one LinkedIn post using a reminder channel, then confirm its URL in Content. You’ll draft, validate, approve and schedule the post before publishing it yourself on LinkedIn. No connected social account is needed for this reminder path.

## Before you start

- For MCP calls, use a token with product-scoped write access for the Content operations below.
- A product on Lepsto, and access to it as at least a Member.
- Either the Lepsto MCP server connected to your agent, or the Workspace app in `app.lepsto.com`. The steps below name the MCP tools; the same operations are in the app's composer, calendar and queue, and over REST at the paths in [Tools and REST API](/acquire/content/tools-and-api).
- **A LinkedIn account you control.** You publish by hand in step 7, so it has to be somewhere you can actually post.

Use the **LinkedIn reminder channel** throughout. It accepts up to 3,000 characters and optional media, so you can complete the tutorial with one text post. See [Other networks](#other-networks) for requirements that change this path.

Replace `<post id>`, `<channel id>`, `<asset id>` and `<publication id>` with the ids returned by Content. Use your own image URL, scheduled time and final LinkedIn post URL.

## 1. See what you can publish to

```text
content_channels_list
```

The first call seeds channels for LinkedIn, X, YouTube, Instagram, Reddit, Facebook, TikTok, Threads, Pinterest, Bluesky and Telegram. Read their capability descriptors in the response.

Take the `id` of the **LinkedIn** channel. Like every seeded channel it is a `member` channel in `reminder` mode with no connector, which is what the rest of this page assumes. Its `maxTextLength` is 3,000.

## 2. Write a post

```text
content_posts_create
  text: "Shipping the new scheduler today. Every publication now settles with one event."
  targets: [{ network: "linkedin" }]
```

Save the returned post id. The new post is a `draft` at version 1 and targets LinkedIn using the base text and default `post` type. Content accepts up to 50,000 Markdown characters, then validates the rendered text against LinkedIn’s 3,000-character limit.

A draft can omit targets, but submission requires at least one target and its required variant fields. Including LinkedIn here makes the draft ready for the later submission step.

## 3. Attach media, if you have any

```text
content_media_upload
  sourceUrl: "https://example.com/screenshot.png"

content_posts_attach_media
  id: <post id>
  mediaIds: [<asset id>]
```

Content fetches the URL server-side. Wait until the asset is `ready` before attaching it. A post carries at most 20 attachments, and LinkedIn accepts 20.

Skip this step for a text-only LinkedIn post. Other post types may require media: YouTube uploads need a video or validation returns `video_required`.

Attaching is an edit, so do it now, before you submit. If your own app already holds the bytes, use the signed-upload path in [Media](/acquire/content/media) instead.

## 4. Check it before you commit to a slot

```text
content_publications_validate
  postId: <post id>
  channels: [{ channelId: <channel id> }]
```

Check `ok`, then review each channel’s `renderedText`, issues, `costUsd` and `costBasis`. Validation makes no publication and does not require approval. A channel outside the post’s targets reports `network_not_targeted`.

The default LinkedIn rates are $0. On X the defaults are $0.015 for plain text and $0.20 for a post with a link; validation reports the current channel rates before scheduling.

## 5. Submit and approve

```text
content_posts_submit
  id: <post id>

content_posts_approve
  id: <post id>
```

Review the post before approving. Submission requires the target and any required variant fields; approval then covers that exact version, including targets and media. Editing it revokes approval and returns it to `pending_approval`. The approver comes from the caller’s credentials, and approval is not available as a workflow step.

## 6. Schedule it

```text
content_publications_schedule
  postId: <post id>
  channelId: <channel id>
  scheduledAt: "2026-10-01T09:00:00.000Z"
```

Save the publication id from the `pending` response. The record holds the approved variant’s rendered text, post type, fields and calculated cost. Do not send `text` with this call: the approved LinkedIn target supplies it. Replace the example time with your intended slot; a past time runs on the next scheduler tick.

## 7. Publish it and confirm

At the scheduled time the reminder channel notifies a person and the publication moves to `publishing`. Publish the post yourself on the network, then close the publication:

```text
content_publications_confirm
  id: <publication id>
  externalUrl: "https://www.linkedin.com/feed/update/<id>"
```

Replace the example URL with the post’s actual LinkedIn URL so you can identify it later. If needed, request another reminder with `content_publications_remind`. Confirmation remains available until the publication is confirmed or cancelled.

## 8. Check the result

```text
content_publications_get
  id: <publication id>
```

Verify `state: "published"`, a populated `publishedAt`, and the `externalUrl` you supplied. These confirm that Content recorded the completed publication.

## 9. Optional: look for metrics

```text
content_metrics_post_series
  id: <publication id>
```

An empty response does not mean publication failed. This tutorial confirms publication, but does not guarantee that analytics will arrive:

- A series is empty until the first snapshot lands. A published post is sampled hourly for its first day, so nothing is there the instant you confirm.
- Where the channel's `analytics` is `none`, no snapshot is ever taken and the series stays empty permanently. LinkedIn and YouTube default to `basic`, X to `full`, and the remaining networks to `none`.

Use the publication state from step 8 to check completion; metrics are a separate result. Sampling runs hourly for the first day, daily to day 7, weekly to day 30, and never after that. When a figure does arrive, a `null` means the network does not report that number — it is not a zero. See [Metrics](/acquire/content/metrics).

## Other networks

When adapting this tutorial, check the target network’s post type and fields as well as its text and media limits:

| Network | What changes |
|---|---|
| **X** | 280 characters, 4 media. A post carrying a link costs $0.20 against $0.015 — validate first |
| **YouTube** | Video required; `title` required (100 characters). The description limit is 5,000 characters, with one media asset and a default daily ceiling of 100 |
| **Instagram** | Will not take a link, and reports no analytics at all. It has no publishing connector, so it cannot be bound for automatic delivery |
| **Reddit** | 40,000 characters; requires `title` (300) and `subreddit` (21); no analytics |
| **Facebook, TikTok, Threads, Pinterest, Bluesky, Telegram** | Reminder-only networks with different post types, fields and limits; check the channel descriptor |

Per-network limits, rates and `analytics` values are in [Channels](/acquire/content/channels).

## Publishing without a person

For automatic posting to a LinkedIn member profile, bind its channel to a platform connector and set its delivery mode to `auto`:

```text
content_channels_bind
  id: <channel id>
  connectorId: <connector id>
  deliveryMode: "auto"
```

Then schedule the approved post as in step 6. Content sends it at the scheduled time; wait for `content/publication.settled` or read the publication state to check the result. Do not use the manual confirmation in step 7 for an automatic publication. YouTube also supports automatic posting; X has adapter code but no registered OAuth app yet, so it stays reminder-only. See [Channels](/acquire/content/channels) for connector restrictions.

## Next steps

- [Posts](/acquire/content/posts): versions and the approval gate in full.
- [Publications](/acquire/content/publications): validation issues, cost, reminders, retries and cancellation.
- [Metrics](/acquire/content/metrics): the sampling schedule, and why a null is not a zero.
