postiee

Post to LinkedIn.
Find it again later.

Post from your terminal. Keep a searchable copy of everything you wrote, because LinkedIn won't let any API read your posts back.

Capability Status

Works for you today

post self-serve
history local
search local
unpublish self-serve

If LinkedIn grants partner access

comments partner only
analytics partner only

Closed, or no API at any tier

read posts back closed
messages / DMs no API
invitations no API
ads separate product
search 144 µs / 1k posts record 1.45 ms limit 150 posts/day token 60 days

macOS · Linux

curl -fsSL https://raw.githubusercontent.com/aswathas/postiee/main/install.sh | sh

Windows · PowerShell

irm https://raw.githubusercontent.com/aswathas/postiee/main/install.ps1 | iex

Read that table again

Most of it is red. That isn't a gap in the product — it's the reason the product is this shape.

LinkedIn has a permission whose entire job is letting an app read your own posts. It isn't slow. It isn't paid. It is closed — the documentation says it is not accepting access requests at all.

So you can write to LinkedIn through an approved app, but nothing can read back what it wrote. Post something, lose the identifier, and that post is gone. Not hidden. Gone.

That is why postiee captures the identifier the moment a post is created and keeps the record itself. The local history isn't a bonus feature. It's the only copy that will ever exist.

Everything green in that table is inside the self-serve tier. Everything amber or red is either partner-gated or does not exist at any tier. The full breakdown is here.

What it does

Five things. No half-finished commands, no stubs that fail on first use.

post

Write a thought, attach one image, choose who can see it.

history

Every post written to your own database the moment it's made.

search

Find that thing you posted in March without scrolling LinkedIn.

unpublish

Delete a post on LinkedIn itself, then drop it from your history. No undo.

forget

Drop a post from your history and leave it live on LinkedIn.

The three traps

Every one of these breaks a naive LinkedIn client, and all three fail silently.

What happens when postiee creates a post Five steps: read credentials, resolve the member URN from the OpenID userinfo endpoint, optionally upload an image in two steps, create the post and read the ID from the x-restli-id response header, then record it in a local SQLite database with full-text search. 1. read credentials ~/.config/postiee/ 2. resolve member URN GET /v2/userinfo openid scope TRAP person ids are app-scoped 3. upload image (opt) initializeUpload → PUT → urn two steps, no finalizeUpload TRAP images have NO finalizeUpload step — that is a Videos API concept 4. create the post POST /rest/posts → 201 Created body is EMPTY — id is in x-restli-id LinkedIn-Version: 202609 THE TRAP THAT MATTERS r_member_social is CLOSED. No API can read your posts back. Miss this id → post is unrecoverable. 5. record locally SQLite + FTS5 index post_id, body, media_urn, created_at → postiee history / search WHY THIS IS THE PRODUCT This local record is the only copy that will ever exist. LinkedIn's own export is static, 72h, desktop-only.

The post ID arrives in a response header while the body is empty. Person IDs are scoped per app, so yours must be resolved at runtime. And URNs in paths need full percent-encoding, which Go's PathEscape does not do. postiee handles all three, and each has a test. How it works walks through them.

Your agent already knows how

Any agent that can run a shell command can drive it.

There's an --agent flag that switches every command to clean JSON, an MCP server, and an installable skill for Claude, Codex, and Cursor. Point it at whatever you already use and posting stops being a separate chore.

postiee post "what I built this week" --image chart.png --alt "signups by week"

Start posting

Ten minutes to set up, including your own LinkedIn app.