Skip to content
postpeg

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:

Shell
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"
  }'
Response · 201 Created
{
  "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"
  }'
Response · 200 OK
{
  "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:

Redirect
https://example.com/settings/social?status=success&account_id=acc_mfz20b5n1s8f4j7xv2p9r6hu1c&platform=instagram

Bluesky 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"
  }'
Response · 201 Created
{
  "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"
      }
    ]
  }'
Response · 201 Created
{
  "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"
Response · 200 OK
{
  "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"
}