> ## Documentation Index
> Fetch the complete documentation index at: https://docs.publishbuddy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish your first post

> An end-to-end walkthrough: pick a profile, attach media, and publish to a social network from the API.

This guide assumes you've already followed the [Quickstart](/quickstart) — you've got an API token, a workspace ID, and at least one connected social profile. Here we'll go deeper into what each step does and the options you have at each one.

## 1. Choose a target profile

A post targets exactly one profile. Decide where the post should land first.

```bash theme={null}
curl "https://api.publishbuddy.com/v1/workspaces/$WORKSPACE_ID/profiles" \
  -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN"
```

Pick the relevant profile from the response (note its `id` and the network it belongs to — this determines what fields apply). See [Social profiles](/concepts/profiles) for the per-network field guide.

## 2. Prepare media (optional)

If your post needs an image or video, upload it first. Uploads go directly to storage in three steps — presign, `PUT` the bytes, then confirm:

```bash theme={null}
# 1. Presign
curl -X POST "https://api.publishbuddy.com/v1/workspaces/$WORKSPACE_ID/media_contents/presign" \
  -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "files": [{ "name": "photo.jpg", "mime": "image/jpeg", "size": 184293 }] }'

# 2. Upload the bytes to the returned upload_url (replaying upload_headers)
curl -X PUT "$UPLOAD_URL" -H "x-amz-acl: public-read" --data-binary @./photo.jpg

# 3. Confirm
curl -X POST "https://api.publishbuddy.com/v1/workspaces/$WORKSPACE_ID/media_contents/confirm" \
  -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "uuids": ["'"$MEDIA_ID"'"] }'
```

The temporary media `id` returned by presign is the `id` you'll reference from the post. See [Upload and manage media](/guides/upload-media) for the full flow, or reuse an existing media library file if you already have one.

<Tip>
  For text-only posts (e.g. an X update with no image), skip this step entirely.
</Tip>

## 3. Create the post

The minimum required fields are `profile_id`, `text` (or `media_content_ids`), and `publish_type`.

<CodeGroup>
  ```bash curl — publish now theme={null}
  curl -X POST "https://api.publishbuddy.com/v1/workspaces/$WORKSPACE_ID/posts" \
    -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "profile_id": "'"$PROFILE_ID"'",
      "text": "Launching today — meet our new feature.",
      "media_content_ids": ["'"$MEDIA_ID"'"],
      "publish_type": "now"
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch(
    `https://api.publishbuddy.com/v1/workspaces/${workspaceId}/posts`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.PUBLISHBUDDY_TOKEN}`,
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify({
        profile_id: profileId,
        text: 'Launching today — meet our new feature.',
        media_content_ids: [mediaId],
        publish_type: 'now',
      }),
    }
  );
  const { data: post } = await res.json();
  console.log(post.id, post.friendly_status);
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      f'https://api.publishbuddy.com/v1/workspaces/{workspace_id}/posts',
      headers={
          'Authorization': f'Bearer {os.environ["PUBLISHBUDDY_TOKEN"]}',
          'Accept': 'application/json',
      },
      json={
          'profile_id': profile_id,
          'text': 'Launching today — meet our new feature.',
          'media_content_ids': [media_id],
          'publish_type': 'now',
      },
  )
  res.raise_for_status()
  post = res.json()['data']
  ```
</CodeGroup>

The response immediately includes the post object with `status: "publishing"`. The actual handoff to the social network happens asynchronously.

## 4. Wait for it to publish

Poll the post until its status leaves `publishing`:

```bash theme={null}
curl "https://api.publishbuddy.com/v1/posts/$POST_ID" \
  -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN"
```

When `status` becomes `published`:

* `url` contains the live link on the social network
* `published_at` contains the publish timestamp
* `social_post` contains network-specific IDs (useful for fetching metrics later)

If `status` becomes `failed`:

* `error_message` explains why
* The most common causes are: expired profile connection, media that violates the network's spec (size, aspect ratio, duration), or rate limiting on the social network's side

<Tip>
  Polling every 5–10 seconds is plenty — most posts complete within seconds. For high-volume integrations, consider polling only the posts you've recently created instead of the whole list endpoint.
</Tip>

## 5. Inspect the result

```bash theme={null}
curl "https://api.publishbuddy.com/v1/posts/$POST_ID" \
  -H "Authorization: Bearer $PUBLISHBUDDY_TOKEN" | jq '{status, url, error_message}'
```

For a successful Instagram post you might see:

```json theme={null}
{
  "status": "published",
  "url": "https://www.instagram.com/p/Cabc123...",
  "error_message": null
}
```

That's it — you've published programmatically. Now learn how to [schedule for later](/guides/schedule-posts) or [queue across the week](/guides/schedule-posts#queue-using-timeslots).
