Quickstart
From nothing to a post on three networks in a handful of requests. Every request goes to https://api.postpeg.com with your key as a bearer token, and every body is JSON.
1. Get a key
Start the 7-day trial in the dashboard and create a key in the dashboard. It’s shown once, so store it as a secret. Keys look like pp_live_ or pp_test_ followed by 32 hex characters. The examples below read it from an environment variable:
export POSTPEG_KEY=pp_test_…Check it works with GET /v1/me, which returns the key, your plan and how many accounts are connected. More in Authentication.
2. Create a profile
A profile is one brand or one of your end users. Social accounts are connected to a profile, so if you run postpeg for many customers, create one profile each and keep your own id in external_id.
POST/v1/profiles
curl https://api.postpeg.com/v1/profiles \
-H "Authorization: Bearer $POSTPEG_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Coffee",
"external_id": "customer_4821"
}'{
"id": "prof_mfz0b7q2k9x4c1v8n3m6a0s5d2",
"name": "Acme Coffee",
"external_id": "customer_4821",
"created_at": "2026-09-24T13:58:40.021Z"
}3. Connect an account
Ask for a connect link, send the account’s owner to it, and they approve access on the network’s own screen. They land back on your redirect_url.
POST/v1/accounts/connect
curl https://api.postpeg.com/v1/accounts/connect \
-H "Authorization: Bearer $POSTPEG_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_id": "prof_mfz0b7q2k9x4c1v8n3m6a0s5d2",
"platform": "instagram",
"redirect_url": "https://example.com/settings/social"
}'{
"url": "https://…",
"expires_at": "2026-09-24T14:15:12.000Z"
}The link is valid for 15 minutes. When the owner is done, the browser is sent to:
https://example.com/settings/social?status=success&account_id=acc_mfz20b5n1s8f4j7xv2p9r6hu1c&platform=instagramBluesky has no OAuth flow here: connect it with the handle and an app password instead, and you get the account straight back.
POST/v1/accounts/bluesky
curl https://api.postpeg.com/v1/accounts/bluesky \
-H "Authorization: Bearer $POSTPEG_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_id": "prof_mfz0b7q2k9x4c1v8n3m6a0s5d2",
"handle": "acmecoffee.bsky.social",
"app_password": "abcd-efgh-ijkl-mnop"
}'{
"id": "acc_mfz21c8w3e6r0t4y2u9i5o1p7a",
"profile_id": "prof_mfz0b7q2k9x4c1v8n3m6a0s5d2",
"platform": "bluesky",
"username": "acmecoffee.bsky.social",
"display_name": "Acme Coffee",
"avatar_url": null,
"provider": "native",
"status": "active",
"connected_at": "2026-09-24T14:00:12.530Z"
}Profiles and accounts covers errors, reconnecting and plan limits.
4. Publish a post
List the account ids, write the post once, and attach media by public https URL. Leave out scheduled_at to publish now.
POST/v1/posts
curl https://api.postpeg.com/v1/posts \
-H "Authorization: Bearer $POSTPEG_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: changelog-2026-10-01" \
-d '{
"account_ids": [
"acc_mfz1x0k27d4q8vbn3c5s0a1p9e",
"acc_mfz1y4r9h2c6t0wqk8e3m7ld5f",
"acc_mfz20b5n1s8f4j7xv2p9r6hu1c"
],
"content": "Scheduled posts now retry on their own when a network has a bad minute. Changelog: https://example.com/changelog #buildinpublic",
"media": [
{
"url": "https://example.com/changelog.png",
"type": "image"
}
]
}'{
"id": "post_mfz2k3v8q1w5e9r4t7y0u2i6o3",
"content": "Scheduled posts now retry on their own when a network has a bad minute. Changelog: https://example.com/changelog #buildinpublic",
"media": [
{
"url": "https://example.com/changelog.png",
"type": "image"
}
],
"platform_options": {},
"status": "publishing",
"scheduled_at": null,
"targets": [
{
"account_id": "acc_mfz1x0k27d4q8vbn3c5s0a1p9e",
"platform": "x",
"status": "queued",
"platform_post_id": null,
"url": null,
"error": null,
"attempts": 0,
"published_at": null
},
{
"account_id": "acc_mfz1y4r9h2c6t0wqk8e3m7ld5f",
"platform": "linkedin",
"status": "queued",
"platform_post_id": null,
"url": null,
"error": null,
"attempts": 0,
"published_at": null
},
{
"account_id": "acc_mfz20b5n1s8f4j7xv2p9r6hu1c",
"platform": "instagram",
"status": "queued",
"platform_post_id": null,
"url": null,
"error": null,
"attempts": 0,
"published_at": null
}
],
"created_at": "2026-09-24T14:02:11.418Z"
}The post is checked against every network before anything is sent. If it doesn’t fit one of them you get a 400 listing every problem, and nothing is published. See Publishing.
5. Check its status
Publishing happens in the background, usually within seconds. Fetch the post to see how each account went: url links to the live post, and error says why a target failed.
GET/v1/posts/{id}
curl https://api.postpeg.com/v1/posts/post_mfz2k3v8q1w5e9r4t7y0u2i6o3 \
-H "Authorization: Bearer $POSTPEG_KEY"{
"id": "post_mfz2k3v8q1w5e9r4t7y0u2i6o3",
"content": "Scheduled posts now retry on their own when a network has a bad minute. Changelog: https://example.com/changelog #buildinpublic",
"media": [
{
"url": "https://example.com/changelog.png",
"type": "image"
}
],
"platform_options": {},
"status": "published",
"scheduled_at": null,
"targets": [
{
"account_id": "acc_mfz1x0k27d4q8vbn3c5s0a1p9e",
"platform": "x",
"status": "published",
"platform_post_id": "1971234567890123456",
"url": "https://x.com/acme/status/1971234567890123456",
"error": null,
"attempts": 1,
"published_at": "2026-10-01T09:00:04.112Z"
},
{
"account_id": "acc_mfz1y4r9h2c6t0wqk8e3m7ld5f",
"platform": "linkedin",
"status": "published",
"platform_post_id": "urn:li:share:7378123456789012345",
"url": "https://www.linkedin.com/feed/update/urn:li:share:7378123456789012345",
"error": null,
"attempts": 1,
"published_at": "2026-10-01T09:00:05.870Z"
},
{
"account_id": "acc_mfz20b5n1s8f4j7xv2p9r6hu1c",
"platform": "instagram",
"status": "published",
"platform_post_id": "18061234567890123",
"url": "https://www.instagram.com/p/DPq7xYzAbCd/",
"error": null,
"attempts": 1,
"published_at": "2026-10-01T09:00:09.304Z"
}
],
"created_at": "2026-09-24T14:02:11.418Z"
}