Post with Go
Use the standard library against the same publish contract as the docs. One request. One Post struct worth of JSON.
Get started free →Free tier. No card · one API for every network · full MCP access
Your services are Go and you want outbound social with idiomatic HTTP, not a zoo of SDK wrappers.
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:
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"text": "Hello from Go 👋",
"profile": "my-brand",
})
req, _ := http.NewRequest("POST", "https://api.postlake.dev/v1/posts", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("POSTLAKE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "hello-go-001")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var post map[string]any
json.NewDecoder(resp.Body).Decode(&post)
// Inspect post["state"] and post["targets"].
}Tool-specific glue on top of the shared setup above:
profile to the Channels name.net/http: no third-party social client.targets (define a small struct if you prefer).Idempotency-Key for workers that retry on timeout.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).resp.Body and check resp.StatusCode before trusting the payload.http.NewRequestWithContext in production workers.processing: poll or webhook before assuming failure.Prefer an assistant over Go code? Point it at MCP (https://api.postlake.dev/mcp). Prefer the dashboard? Use Quickstart → No code.
Marshal JSON { text, profile }, POST to /v1/posts with Authorization: Bearer. Decode the Post and read targets for live URLs and errors.
No. net/http is enough; PostLake is one REST endpoint for every network.
Add scheduledAt (UTC, or a naive local time plus timezone) to the body map. Credits charge at fire time.
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 →