Post with LangChain
Wrap PostLake as one tool, or skip the wrapper and connect MCP. Either way the agent sees the same normalised Post response the Publishing docs describe.
Get started free →Free tier. No card · one API for every network · full MCP access
You're building a LangChain agent that should post or schedule without registering nine social tools.
Same setup the docs quickstart uses. Do this once:
my-brand) and connect at least one account. Bluesky is the fastest first channel: no app review. Instagram/TikTok/Facebook need each platform's review before API posting.sk_live_…). It shows once; treat it like a password.Connect accounts once in the dashboard, then this is the publish call:
from langchain_core.tools import tool
import os, requests, uuid
@tool
def post_to_social(text: str, platforms: list[str] | None = None) -> dict:
"""Publish via PostLake. Returns the full Post object (check targets[])."""
body = {"text": text, "profile": "my-brand"}
if platforms:
body["platforms"] = platforms # filter only
r = requests.post(
"https://api.postlake.dev/v1/posts",
headers={
"Authorization": f"Bearer {os.environ['POSTLAKE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json=body,
)
r.raise_for_status()
return r.json()
# model.bind_tools([post_to_social]), or use MCP instead (no wrapper)Tool-specific glue on top of the shared setup above:
https://api.postlake.dev/mcp) if your LangChain host supports it, OAuth. No key in the tool./v1/posts and returns the full JSON (not just "ok").state and each targets[].state before claiming success.Idempotency-Key (new UUID per logical post) so tool retries don't double-post.You get one Post with an overall state and a targets[] array. One entry per account. Always check each target; partial success is normal.
{
"id": "post_a1b2c3",
"state": "partial",
"targets": [
{ "platform": "bluesky", "state": "published", "url": "https://bsky.app/…" },
{ "platform": "linkedin", "state": "failed",
"error": { "type": "invalid_request", "message": "…", "retryable": false } }
]
}published: every target succeeded. partial: some published, some failed. failed: none published. processing: still going (async networks like TikTok).Idempotency-Key header on writes so a retry never double-posts.Same rules as the docs. Pick one addressing style:
"profile": "my-brand" posts to every account under that profile (the name on Channels)."platforms": ["bluesky", "linkedin"] narrows that set. It is a filter, not a selector: if you have two Pinterest boards, both match pinterest."accounts": ["acc_…"] for exact channels (copy an id on Channels, or GET /v1/social-accounts).profile? PostLake uses that profile. Multiple profiles and you omit it? You'll get an error that names them.See Publishing: where to post.
scheduledAt as UTC (trailing Z), or a naive local time plus timezone (IANA, e.g. Europe/London). Credits charge when it fires. Scheduling docs · scheduling guide.POST /v1/media, then pass the med_… id in media. Media docs.textOverrides (e.g. shorter text for X). Per-network options: platformOptions (Pinterest boardId, TikTok privacy, …). Live option lists: GET /v1/platforms/{platform}.POST /v1/posts/validate runs the same checks without publishing (free).POST /v1/posts/validate (or the MCP validate tool) before publish.Want zero wrapper code? Connect the hosted MCP server (https://api.postlake.dev/mcp) over OAuth. Same accounts and responses as this API path. Agents overview.
Either connect PostLake MCP, or bind one tool that POSTs /v1/posts with text + profile and returns the JSON. The agent then handles every connected network through that one tool.
No. One tool calling PostLake covers the profile. Use platforms only as an optional filter.
Yes. Point the host at https://api.postlake.dev/mcp and complete OAuth. Tools for publish, schedule, validate, and analytics appear without a custom wrapper.
These guides stay short on purpose. Canonical behaviour lives here:
Also: Media · Errors · MCP · Analytics
All guides · Full docs · llms.txt · Markdown
Stuck? The docs are the source of truth, start at Publishing.
Open the dashboard →