URL: /code/apps/social/publish · Id: `page:/code/apps/social/publish`

# Post.publish

> Put a Post live from your own LinkedIn, X, Reddit or Product Hunt account.

`POST https://api.sb/{your-domain}/posts/publish`

Put a [Post](https://startupbuilder.dev/business/nouns/post) live from your own LinkedIn, X, Reddit or Product Hunt account.

- **Action class:** `publish`
- **Approval:** Publishes on its own
- **Emits:** `Post.published`
- **Sites:** [9 sites](#on-each-venue)
- **Price:** Free

```text title="Run it · Ask"
Publish my post about the renewal-notice checklist from my LinkedIn.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Post.publish({ post: 'post_Lk4vR8sQ2m' })
```

```json title="Run it · Response"
{
  "api": "https://api.sb/leaseline.app",
  "post": {
    "$id": "https://api.sb/leaseline.app/posts/post_Lk4vR8sQ2m",
    "$type": "Post",
    "handle": "https://api.sb/leaseline.app/handles/handle_Li9fB3wE6y",
    "format": "text",
    "status": "published",
    "claims": [
      {
        "says": "Leaseline sends every renewal notice 60 days out, per owner.",
        "record": "https://api.sb/leaseline.app/offers/offer_Ls1mP4qR8v"
      }
    ],
    "outside": null,
    "reach": null
  },
  "action": {
    "operation": "Post.publish",
    "mode": "try",
    "status": "done"
  }
}
```

Puts a Post live from your own account on one site, once every claim it makes reads from a live record. [The hub’s definition of `Post.publish`](https://startupbuilder.dev/business/nouns/post/publish).</HubDefinition>

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `post` | `Post` | Required | The Post to publish. |

## Outputs
| Field | Type | What `null` means |
| --- | --- | --- |
| `post` | `Post` | Never null. |
| `post.status` | `string` | Never null: `published` once the network confirms the Post is live. |
| `post.handle` | `Handle` | Never null: the account it went out from. |
| `post.format` | `string` | Never null: the network’s format, such as a text post, a thread or a launch. |
| `post.outside` | `json` | Null in try mode and until the network returns its own id for the Post. |
| `post.reach` | `integer`, in people | Null until the network reports a first reading; `Metric.query` reads it over time. |

## Effects
**Action class**

`publish`: an approval rule on `publish` can make it wait for your OK

**Modes**

- `try`: Runs every check the publish meets and returns the Post as the network would show it; nothing is posted or stored.
- `send`: Returns a pending Action at once; `Post.published` fires when the network confirms the Post is live.
- `do`: Holds the call for the Mode’s hold time, then returns `202` with the pending Action while its delay window runs.

**Idempotency**

Only with a key: the same `Idempotency-Key` and input return the first Action, and the same key with other input returns `409`. A Post that already holds the network’s id in `outside` is never posted twice: a repeat returns it as it stands.

**Saved or shared**

The Post keeps the network’s id and, as the network reports them, its reach and replies. Each reply arrives as a Message threaded to the Post.

**Emits**

`Post.published` with `{ post, handle, venue, outside, publishAt }`

**Performed by**

[Content lead](https://startupbuilder.dev/agents/marketing/content-lead)

**Price**

Free on your own account: the platform charges nothing for the call.

**Standing approval**

It goes out on its own, inside the Limits of your standing approval: a [`Mandate`](https://startupbuilder.dev/business/nouns/mandate) granted with [`Mandate.authorize`](/code/integrations/calls/authorize). One approval rule makes it wait for your OK.

## On each Venue
| Site | Fields | Formats |
| --- | --- | --- |
| [Facebook](#facebook) | `body`, `link` | 1 |
| [Google Business Profile](#google-business-profile) | `body`, `link`, `media` | None. |
| [Hacker News](#hacker-news) | `title`, `link`, `body` | 2 |
| [Instagram](#instagram) | `body`, `format`, `media` | 4 |
| [LinkedIn](#linkedin) | `body`, `media`, `handle`, `status` | 3 |
| [Reddit](#reddit) | `title`, `body`, `handle` | 3 |
| [TikTok](#tiktok) | `body`, `status` | 2 |
| [X](#x) | `body`, `media`, `format` | 2 |
| [YouTube](#youtube) | `title`, `body`, `tags`, `status` | 1 |

### Facebook
Through [Facebook](/code/integrations/facebook).

| Our field | Facebook’s field |
| --- | --- |
| `Post.body` | `message` |
| `Post.link` | `link` |

- **Your role on the page.** Posting needs a role that can post; a role removed in Meta’s settings ends the connection with `needs-reauth`.

Feed image ad, 1440 × 1800, 4:5

### Google Business Profile
Through [Google Business Profile](/code/integrations/google-business-profile).

| Our field | Google Business Profile’s field |
| --- | --- |
| `Post.body` | `localPost.summary` |
| `Post.link` | `localPost.callToAction.url` |
| `Post.media` | `localPost.media` |

- **A new profile.** Shows nothing your agents post until Google verifies it.

### Hacker News
Through [Hacker News](/code/integrations/hacker-news).

| Our field | Hacker News’s field |
| --- | --- |
| `Post.title` | `title` (Begins “Show HN:” and runs at most 80 characters) |
| `Post.link` | `url` (The page a visitor can try at once) |
| `Post.body` | `first comment` (How you built it and what you want to hear, posted as the first comment) |

- **What goes up as a Show HN.** Something people can try, ideally with no signup or email; a page that only collects addresses goes up as a regular post.
- **Votes.** Your agent never asks anyone to upvote or comment, on Hacker News or off it.
- **A second Show HN.** Only after a real change to the product, never the same title again.
- **Launch-day replies.** The agent drafts a reply to every comment within 15 minutes for the first 6 hours, and posts it under your standing approval.

Show HN Comment

### Instagram
Through [Instagram](/code/integrations/instagram).

| Our field | Instagram’s field |
| --- | --- |
| `Post.body` | `caption` (2,200 characters, 30 hashtags, 20 mentions) |
| `Post.format` | `media_type` (IMAGE, CAROUSEL, REELS or STORIES) |
| `Post.media` | `image_url or video_url` (A carousel holds up to 20 items, cropped to the first one’s ratio) |

- **Posts through the API.** 50 in a moving 24 hours, and a carousel counts as one; the agent reads what is left before each post.
- **Account type.** A professional account; a personal account refuses with `account-not-connected` until it switches.

Feed image, 320 to 1,440 pixels wide, 4:5 to 1.91:1 Carousel Reel, 9:16 Story, 9:16

### LinkedIn
Through [LinkedIn](/code/integrations/linkedin).

| Our field | LinkedIn’s field |
| --- | --- |
| `Post.body` | `commentary` (Mentions and hashtags keep LinkedIn’s own annotation format) |
| `Post.media` | `content.media.id` (An image, video or document is uploaded first and posted by its URN) |
| `Post.handle` | `author` (urn:li:person for you, urn:li:organization for your page) |
| `Post.status` | `lifecycleState` (Always PUBLISHED on create; a scheduled Post is held by the platform and published at its time) |

- **Replies.** Read from your LinkedIn inbox every 30 minutes on weekdays, since LinkedIn sends no webhook for member messages.
- **Company page posts.** Need your administrator or content admin role on the page.

Feed post Single image ad, 1200 × 628, 1200 × 1200 or 720 × 900 Document ad

### Reddit
Through [Reddit](/code/integrations/reddit).

| Our field | Reddit’s field |
| --- | --- |
| `Post.title` | `title` (300 characters; a comment has none) |
| `Post.body` | `body or comment text` |
| `Post.handle` | `subreddit or parent post` |

- **Before each post.** The agent rereads the subreddit’s sidebar and pinned posts and drafts to fit them; a post the moderators remove returns `venue-rejected` with their reason.
- **A new account.** Posts help-only answers for its first 2 to 4 weeks in a community; mentions of your product wait until it has history there.
- **Mentions.** At most 1 answer in 4 names your product, and only in subreddits that welcome it.
- **Pace.** 10 answers a week across your subreddits, each within 6 hours of the post.
- **Removals.** The agent checks each post signed out every week; a removed post becomes `Post.withdrawn` with the reason it can read.

Text or link post Comment Promoted post

### TikTok
Through [TikTok](/code/integrations/tiktok).

| Our field | TikTok’s field |
| --- | --- |
| `Post.body` | `post_info.title` (The caption: 2,200 characters, hashtags included) |
| `Post.status` | `privacy_level` (One of the options your account reports; PUBLIC_TO_EVERYONE when the browser agent posts) |

- **Public posts.** Each clip reaches your TikTok inbox by API, and the browser agent presses Post as you, so it goes public on your account.
- **Posts a day.** TikTok caps posts a day per creator; past it a post refuses with `quota-reached` and runs the next day.

Video Spark Ad

### X
Through [X account](/code/integrations/x).

| Our field | X account’s field |
| --- | --- |
| `Post.body` | `text` (280 weighted characters; a link counts as 23 wherever it points) |
| `Post.media` | `media.media_ids` (Up to 4 images, or 1 GIF, or 1 video, uploaded first) |
| `Post.format` | `reply.in_reply_to_tweet_id` (A thread is a chain of posts, each replying to the last) |

- **Each post or reply.** X bills 1.5¢ through its API, and 20¢ when the text carries a link; the price shows before the call runs.
- **Monthly spend.** Your standing approval holds the month’s X spend; a post past it waits with `over-mandate`.
- **Replies to strangers.** Only to posts that describe your problem, and never twice in one thread without an answer back.

Standard post Promoted post

### YouTube
Through [YouTube](/code/integrations/youtube).

| Our field | YouTube’s field |
| --- | --- |
| `Post.title` | `snippet.title` (100 characters, no < or >) |
| `Post.body` | `snippet.description` (5,000 bytes, so accented letters count double) |
| `Post.tags` | `snippet.tags` (500 characters in all, commas included) |
| `Post.status` | `status.privacyStatus` (public, unlisted or private; public when none is set) |

- **Upload quota.** YouTube meters uploads a day; an upload past the day’s quota refuses with `quota-reached` and runs after the reset at midnight Pacific.
- **Custom thumbnails and videos over 15 minutes.** Need a verified channel; the agent checks before it uploads and says which step is missing.
- **Comments.** Read every 30 minutes, since YouTube sends no webhook for comments.

Video

## What try does
| Account | What try does |
| --- | --- |
| [Facebook](/code/integrations/facebook#try) | Returns the post as your page will show it, with the time it goes live; nothing is posted or scheduled. |
| [Google Business Profile](/code/integrations/google-business-profile#try) | Returns the update or the answer as it will show on your profile; nothing posts. |
| [Hacker News](/code/integrations/hacker-news#try) | Returns the title, the link and the first comment as Hacker News will show them, checked for the Show HN prefix, the 80-character title and a link people can try; nothing posts. |
| [Instagram](/code/integrations/instagram#try) | Checks each image and video against Instagram’s specs and returns the post as it will show; no container is staged and nothing publishes. |
| [LinkedIn](/code/integrations/linkedin#try) | Posts render as LinkedIn will show them and nothing is sent; requests and messages return each person, the note and the day it goes, and no browser session opens. |
| [Reddit](/code/integrations/reddit#try) | Returns the answer as Reddit will show it, fitted to the subreddit’s sidebar and pinned posts, with the steps the browser agent would take; no session opens and nothing posts. |
| [TikTok](/code/integrations/tiktok#try) | Checks the clip and caption against what your account reports it allows, and returns the post as it will show; nothing reaches your inbox. |
| [X account](/code/integrations/x#try) | Renders each post at X’s weighted count, with links at 23 characters, and returns the price X would bill; nothing is posted and nothing is billed. |
| [YouTube](/code/integrations/youtube#try) | Checks the file, title, description and tags against YouTube’s limits and returns the video page as it will read; nothing uploads and no quota is spent. |

## Refuses when
| Check | Error | Fix | Then call |
| --- | --- | --- | --- |
| A Post with a claim that resolves to no live record | [`claims-resolve`](/code/refusals#check-claims-resolve) | Cut the claim, or name the record that backs it. | [`Content.draft`](https://startupbuilder.dev/business/nouns/content/draft) |

Every call through an account can also meet [the shared account refusals](/code/refusals#accounts): [`grant-missing`](/code/refusals#grant-missing), [`needs-reauth`](/code/refusals#needs-reauth), [`account-not-connected`](/code/refusals#account-not-connected), [`over-mandate`](/code/refusals#over-mandate), [`quota-reached`](/code/refusals#quota-reached), [`venue-rejected`](/code/refusals#venue-rejected).

```json
{
  "type": "https://startupbuilder.dev/errors/claims-resolve",
  "title": "Claim with no record",
  "status": 422,
  "detail": "A Post with a claim that resolves to no live record.",
  "fix": "Cut the claim, or name the record that backs it.",
  "next": {
    "verb": "Content.draft",
    "input": {}
  },
  "instance": "https://api.sb/leaseline.app/actions/action_1"
}
```

## When it waits
| Fact | Value |
| --- | --- |
| `send` returns | A pending Action, `reason: 'awaitingTime'` while its delay window runs, then `running` until the network returns the Post’s id. |
| Delivered by | `Post.published` |

## Example

**Ask**

```text
Publish my post about the renewal-notice checklist from my LinkedIn.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/posts/publish?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"post":"post_Lk4vR8sQ2m"}'
```

The response is the one in the Run it window above.

**CLI**

```bash
npx startup-builder do Post.publish --try --data '{"post":"post_Lk4vR8sQ2m"}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Post.publish({ post: 'post_Lk4vR8sQ2m' })"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const result = await $.try.Post.publish({ post: 'post_Lk4vR8sQ2m' })
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/data#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `post_test_published` | `{"status":"published","outside":{"linkedin":"urn:li:share:7000000000000000001"}}` | Publishes to a test account and returns the same network id every time, so your agent’s follow-up steps run. |
| `post_test_unresolved` | Refuses with [`claims-resolve`](/code/refusals#check-claims-resolve) | Its one claim names no live record, so the publish refuses before anything reaches the network. |
| `post_test_removed` | Refuses with [`venue-rejected`](/code/refusals#venue-rejected) | The network takes the Post down as it goes live, and its reason comes back in `detail`, so your removal path runs. |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#max-cost) | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
| `rate-limited.test` | Refuses with [`rate-limited`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#sources-busy) | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |

## Used by
- **Workflows:** [Directory submissions](/capabilities/earn-first-links/directory-submissions)
- **Agent tasks:** [Answer threads](/capabilities/publish-and-post/answer-threads) · [Launch day](/capabilities/earn-first-links/launch-day)
- **Jobs:** [Earn first links](/capabilities/earn-first-links) · [Publish and post](/capabilities/publish-and-post)
- **Integrations:** [Facebook](/code/integrations/facebook) · [Google Business Profile](/code/integrations/google-business-profile) · [Hacker News](/code/integrations/hacker-news) · [Instagram](/code/integrations/instagram) · [LinkedIn](/code/integrations/linkedin) · [Reddit](/code/integrations/reddit) · [TikTok](/code/integrations/tiktok) · [X account](/code/integrations/x) · [YouTube](/code/integrations/youtube)

## Related
[`Post.schedule`](/code/apps/social/schedule)