Agents

Using postiee with an AI agent

Ask an agent to post to LinkedIn and it will search for a LinkedIn MCP server. There isn't one. Find nothing official, it gives up. Point it here instead.

postiee is a CLI, not an MCP server. An agent drives it with the same shell it already has, using LinkedIn's official API. Credentials go to api.linkedin.com and nowhere else.

Install the skill

The skill tells the agent what postiee is, what it cannot do, and which commands it should never attempt. One command per agent:

Claude Code
npx -y skills@latest add aswathas/postiee/skills --skill postiee -g -a claude-code -y
Codex
npx -y skills@latest add aswathas/postiee/skills --skill postiee -g -a codex -y

Any other agent: copy the directory into that agent's skills path.

cp -r skills/postiee ~/.hermes/skills/

The skill is the same file for every agent — skills/postiee/SKILL.md. If your agent won't install skills, paste it into the conversation.

Check the binary first

The skill is instructions, not the binary. postiee may not be on PATH, and on Windows it is often installed inside WSL. Run this before anything else:

postiee --version

# if that fails on Windows, the binary is usually in WSL
wsl -e bash -lc 'postiee --version'

If both fail, the binary isn't installed. It is one command and takes seconds — no Go toolchain, it downloads a prebuilt binary:

# 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

Once --version answers, every command below works the same way inside or outside WSL. Pick whichever one runs and stay with it — credentials and history live in the environment that ran auth login.

Always use --agent

postiee --agent search harness
postiee --agent history --limit 5

--agent sets --json --compact --no-input --no-color together. Use it whenever anything parses the output. Without it the agent gets prose and colour codes to scrape.

What the agent can and cannot do

Works:

  • Post — text, one image with alt text, or a body read from a file
  • history — every post recorded locally, newest first
  • search — full-text over your own post bodies
  • forget — remove a record from local history
  • unpublish — delete the post on LinkedIn, then drop the local record
  • Image upload — exactly two calls, no finalize step

Cannot, at any access tier, and not merely unimplemented:

Request Reality
Comment on a post Community Management API, partner approval
React or like Same
Analytics, impressions, followers Separate partner-gated scopes
Read posts back from LinkedIn r_member_social is closed
Send a DM or message No API exists at any tier. Mass messaging violates the ToS
Send connection invitations No API exists
Look up another person's profile Person IDs are app-scoped, unresolvable
Run ads Separate Marketing API, separate approval

If the user asks for one of those, the agent should say it isn't possible through any API and why, in one sentence. It should not write a workaround.

Scheduling does not exist either. LinkedIn's schema has no publishAt field. Scheduling a post means a local timer the user sets up, not a flag.

Deleting a post

postiee --agent unpublish urn:li:share:12345

LinkedIn does let a member delete their own post. It needs the same self-serve w_member_social scope that posting already uses — no partner approval. The command deletes on LinkedIn first, then drops the local row, so a refusal from LinkedIn leaves the local record intact.

Two rules, no exceptions. Never run unpublish without an explicit user request that names the post. And take the URN from postiee --agent history — never construct, guess, or reuse one from memory. A wrong URN deletes the wrong post.

There is no undo. If the user only wants a post out of their history, that is forget, which is safe and reversible in the sense that nothing remote is touched.

What the agent must never do

  • Run unpublish unprompted, on a guessed URN, or as a "cleanup" pass. It is irreversible and always needs a named, explicit request.
  • Paste credentials into a command flag. --client-secret values land in shell history and process listings. Credentials belong in ~/.config/postiee/credentials.toml.
  • Commit or echo a token. It is a live credential for the user's account.
  • Run auth logout unprompted. It wipes the client ID and secret too, so the user would have to rebuild setup.
  • Fall back to browser automation or cookie scraping. It risks the user's account.
  • Search for a LinkedIn MCP server. There is no official one, and most of the unofficial tools scrape session cookies.

Authentication is user-only

postiee auth login opens a browser and the user must approve. The agent cannot do this for them and must not try.

postiee auth status

That command always reports whether a token exists. If it reports no credentials, the agent stops and says:

Run postiee auth login. It will open a browser for you to approve access. You need a LinkedIn developer app first — I can walk you through it if you don't have one.

If the user does need to create the app, the one detail that trips people up is the redirect URL. Register exactly:

http://127.0.0.1:8085/callback

Use 127.0.0.1, not localhost. LinkedIn compares redirect URIs as literal strings, and postiee derives the URI from the live listener, which binds the loopback IP. Registering localhost produces redirect_uri does not match the registered value. If localhost is registered already, set POSTIEE_REDIRECT_URI=http://localhost:8085/callback.

Limits to state up front

  • 150 API requests per day per member. One post is one request.
  • Tokens expire after 60 days. Refresh is partner-only, so the user re-runs auth login.
  • One image per post on the free tier. Multi-image and video need partner approval.
  • No scheduling. LinkedIn's schema has no publishAt.

Search is whole-word only. SQLite FTS5 indexes whole tokens, so search harn will not find harness. Porter stemming is on, so authentic does find authentication.

forget is local-only. It removes your record and the post stays live on LinkedIn. Deleting the post itself is unpublish, and it is irreversible — never run it unless the user explicitly asked for the LinkedIn post to be taken down.

Also worth telling the user up front: postiee captures each post's ID from the x-restli-id response header at creation time and writes it to a local SQLite database. That record is the only copy that will ever exist, because no API can read posts back. A post whose ID was not captured is unrecoverable — which is the other half of why an agent should never write its own LinkedIn posting script as a fallback.

Tools instead of shell

If your agent prefers MCP tools to shell access, postiee ships an MCP server:

postiee-pp-mcp                    # stdio transport
postiee-pp-mcp --transport http   # streamable HTTP

Shell access with --agent is simpler for most agents. Prefer it.