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

# API Overview

> Getting started with the PostQueen Public API

Scheduling, media and analytics are all public API calls, which is why agents drive her so well. REST at `https://api.postqueen.ai/public/v1`, with your API key in the `Authorization` header. That key is the only setup: your first scheduled post is one `POST` away.

<Frame>
  <img src="https://mintcdn.com/forceplay/B3-2tTHDDqEkLyvR/images/brand/api-client.svg?fit=max&auto=format&n=B3-2tTHDDqEkLyvR&q=85&s=300acef36db317b1eb566e47d5101d85" alt="POST /public/v1/posts: the request body and the 201 response with the scheduled post ids" width="620" height="508" data-path="images/brand/api-client.svg" />
</Frame>

## SDKs & Integrations

<CardGroup cols={2}>
  <Card title="NodeJS SDK" icon="node-js" href="/public-api/sdk">
    Call the API from Node with the official typed client.
  </Card>

  <Card title="n8n Node" icon="https://mintcdn.com/forceplay/3MbWAJB4xKJ_Rxe0/images/brand/icon-n8n.svg?fit=max&auto=format&n=3MbWAJB4xKJ_Rxe0&q=85&s=25d4f09b68dcfa3384c00cdc67569bd5" href="/automation/n8n" width="30" height="30" data-path="images/brand/icon-n8n.svg">
    Drop the community node into any workflow, no code needed.
  </Card>

  <Card title="Zapier" icon="https://mintcdn.com/forceplay/3MbWAJB4xKJ_Rxe0/images/brand/icon-zapier.svg?fit=max&auto=format&n=3MbWAJB4xKJ_Rxe0&q=85&s=9b00a49e4b41097e9b0c1462c85f0605" href="/automation/zapier" width="30" height="30" data-path="images/brand/icon-zapier.svg">
    Wire posts into thousands of apps with a webhook and your key.
  </Card>

  <Card title="Make" icon="https://mintcdn.com/forceplay/3MbWAJB4xKJ_Rxe0/images/brand/icon-make.svg?fit=max&auto=format&n=3MbWAJB4xKJ_Rxe0&q=85&s=70878cabd9801ee102878555158bff73" href="/automation/make" width="30" height="30" data-path="images/brand/icon-make.svg">
    Call her from any scenario with the built-in HTTP module.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/automation/webhooks">
    She calls your endpoint the moment a post goes live.
  </Card>

  <Card title="RSS AutoPost" icon="rss" href="/automation/rss-autopost">
    She reads the feed hourly and turns the newest item into a draft.
  </Card>
</CardGroup>

## Authentication

Two ways to authenticate, and both ride in the same `Authorization` header:

### API Key

Get your API key from **Settings > Developers > Public API**. You will see the Developers tab if you are an organization admin on a plan that includes the public API. Include the key in the `Authorization` header:

```bash theme={"system"}
curl -H "Authorization: your-api-key" https://api.postqueen.ai/public/v1/integrations
```

### OAuth2 Token

Building an app for other PostQueen users? Use [OAuth2 Authentication](/public-api/oauth) to get tokens that act on behalf of users, so nobody has to hand over an API key. OAuth tokens start with `pos_` and are used the same way:

```bash theme={"system"}
curl -H "Authorization: pos_your-oauth-token" https://api.postqueen.ai/public/v1/integrations
```

### Which one you want

Both reach the same endpoints. The difference is whose account you are acting on.

|             | API key                                                                                       | OAuth token                                                                |
| ----------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Acts as     | Your own organization                                                                         | The organization of the user who authorized you                            |
| Good for    | Your own scripts and automation                                                               | A product other PostQueen users sign in to                                 |
| Lifetime    | No expiry                                                                                     | Follows the grant. The code that produced it is single-use and short-lived |
| Rotating it | Regenerate in **Settings > Developers > Public API**, which takes the old one out immediately | Rotate the client secret in your OAuth app settings                        |

<Note>
  An API key has no expiry and nothing rotates it on a schedule. It stops working when you
  regenerate it and not before, so treat it as a password rather than a session.
</Note>

## Base URL

| Environment     | Base URL                                                                                         |
| --------------- | ------------------------------------------------------------------------------------------------ |
| PostQueen Cloud | `https://api.postqueen.ai/public/v1`                                                             |
| Self-hosted     | `{NEXT_PUBLIC_BACKEND_URL}/public/v1`, for example `https://postqueen.example.com/api/public/v1` |

## Rate Limits

<Info>
  **30 requests per hour** limit applies only to the create post endpoint.

  That is not a ceiling of 30 posts. Each call counts once no matter how much it carries, so put several posts in one request when you need volume.
</Info>

The rate limit is one global value for the whole instance, and it is the same on every subscription plan. Plans differ on channel counts and posts per month instead. Self-hosting? You set the per-hour limit with the `API_LIMIT` env var. The official Docker Compose sets `API_LIMIT: 30`; with the variable unset the backend defaults to 90 requests per hour. Full env var list: [Configuration Reference](/configuration/reference).

## Errors

| Status                  | Meaning                                                                                                                                                                                                                                                                                                                              |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400 Bad Request`       | The request body or path parameter is malformed (wrong shape, unknown enum, missing required field).                                                                                                                                                                                                                                 |
| `401 Unauthorized`      | `Authorization` header is missing or the API key is unrecognized.                                                                                                                                                                                                                                                                    |
| `402 Payment Required`  | Your plan is out of monthly posts or channels.                                                                                                                                                                                                                                                                                       |
| `404 Not Found`         | The endpoint does not exist, or the path parameter (integration ID, post ID) was correct format but no row matched.                                                                                                                                                                                                                  |
| `413 Payload Too Large` | Your request body exceeded the default JSON limit of roughly 100 kB. The 50 MB limit you may have read about is on the internal routes the web UI uses, not on this one, so inlining a single base64 image is enough to trip it. Upload first and send the returned `path`. See [Uploads troubleshooting](/troubleshooting/uploads). |
| `429 Too Many Requests` | You exceeded `API_LIMIT` per hour on the create-post endpoint.                                                                                                                                                                                                                                                                       |
| `5xx`                   | Server error: retry with exponential backoff. Also what you get when you name a row belonging to another organization: it is invisible rather than forbidden, so the handler fails on the empty result. There is no `403` on this API.                                                                                               |

For `DELETE` endpoints, you can treat `404` as already deleted: the row is gone either way, so it is safe to ignore. A `500` *can* mean the same thing today because of a [known issue](/troubleshooting/known-issues) where a missing post ID surfaces as 500 instead of 404, but only if the error matches that specific signature. Treat other `500` responses as real server errors: log them, retry with exponential backoff, and do not swallow them quietly.

## Terminology

<Note>
  The PostQueen UI uses the term **channel**, while the API uses **integration**. They refer to the same thing: a connected social media account.
</Note>

## Generate Output

Let the app write the JSON for you. It is the same composer you use to schedule a post, except it stops one step short: instead of putting the post on your calendar, it hands over the payload for your API request.

* For cloud, make sure you are logged in.
* For local, make sure your PostQueen server is running and you are logged in.

<Tabs>
  <Tab title="Cloud">
    <iframe src="https://app.postqueen.ai/modal/dark/all" style={{zoom: 0.5, height: 1000, width: '100%'}} />

    <a href="https://app.postqueen.ai/modal/dark/all" target="_blank" rel="noopener noreferrer" style={{display: 'block', marginTop: '10px', textAlign: 'center'}}>
      Open in full screen
    </a>
  </Tab>

  <Tab title="Local (Make sure your local server is running)">
    <iframe src="http://localhost:4200/modal/dark/all" style={{zoom: 0.5, height: 1000, width: '100%'}} />

    <a href="http://localhost:4200/modal/dark/all" target="_blank" rel="noopener noreferrer" style={{display: 'block', marginTop: '10px', textAlign: 'center'}}>
      Open in full screen
    </a>

    <p style={{fontSize: '0.85em', textAlign: 'center', marginTop: '6px'}}>
      Source-mode default. Running the bundled Docker image (<code>ghcr.io/gkhankinay/postqueen-app</code>)? The official compose maps to <code>[http://localhost:4007](http://localhost:4007)</code>.
    </p>
  </Tab>
</Tabs>

## Supported Platforms

She posts to 30+ networks, and each one has its own settings schema. Every post you send carries a `settings` object. It usually names the provider in a `__type` field, though the server overwrites that field with the provider of the channel you addressed, so what has to match your channel is the **shape** of the settings, not the string you put in `__type`. Channels whose provider needs no settings can send an empty object.

### Platforms with custom settings

<Tabs>
  <Tab title="Social">
    | Platform              | `__type`               | Key settings                      |
    | --------------------- | ---------------------- | --------------------------------- |
    | X (Twitter)           | `x`                    | `who_can_reply_post`, `community` |
    | LinkedIn              | `linkedin`             | `post_as_images_carousel`         |
    | LinkedIn Page         | `linkedin-page`        | `post_as_images_carousel`         |
    | Facebook              | `facebook`             | `url` (optional)                  |
    | Instagram (FB-linked) | `instagram`            | `post_type`, `collaborators`      |
    | Instagram Standalone  | `instagram-standalone` | `post_type`, `collaborators`      |
    | Warpcast (Farcaster)  | `wrapcast`             | `subreddit[]` (channels)          |

    The Farcaster identifier is spelled `wrapcast`, not `warpcast`. That is what `GET /integrations` returns and what the settings schema is keyed on. On `POST /posts` the server replaces whatever you put in `__type` with the connected channel's own identifier, so a wrong spelling is not rejected, it is simply ignored.
  </Tab>

  <Tab title="Video">
    | Platform | `__type`  | Key settings                                                                                                                           |
    | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
    | YouTube  | `youtube` | `title`, `type`, `selfDeclaredMadeForKids`, `thumbnail`, `tags`                                                                        |
    | TikTok   | `tiktok`  | `privacy_level`, `duet`, `stitch`, `comment`, `autoAddMusic`, `brand_content_toggle`, `brand_organic_toggle`, `content_posting_method` |

    TikTok applies every setting except the title only when `content_posting_method` is `DIRECT_POST`; `duet`/`stitch` are video-only and `autoAddMusic` photo-only. Every field: [TikTok settings](/public-api/providers/tiktok).
  </Tab>

  <Tab title="Community">
    | Platform | `__type`   | Key settings                                                                      |
    | -------- | ---------- | --------------------------------------------------------------------------------- |
    | Reddit   | `reddit`   | `subreddit[]` with `title`, `type`, `flair`                                       |
    | Lemmy    | `lemmy`    | `subreddit[]` with `id`, `title`, `url`                                           |
    | Discord  | `discord`  | `channel`                                                                         |
    | Slack    | `slack`    | `channel`                                                                         |
    | Skool    | `skool`    | `group`, `label`, `title`                                                         |
    | Whop     | `whop`     | `company`, `experience`, `title`                                                  |
    | Moltbook | `moltbook` | `submolt` (required)                                                              |
    | MeWe     | `mewe`     | `postType` (`timeline` or `group`), `group` (required when `postType` is `group`) |
  </Tab>

  <Tab title="Design">
    | Platform  | `__type`    | Key settings                               |
    | --------- | ----------- | ------------------------------------------ |
    | Pinterest | `pinterest` | `board`, `title`, `link`, `dominant_color` |
    | Dribbble  | `dribbble`  | `title`, `team`                            |
  </Tab>

  <Tab title="Blogging">
    | Platform  | `__type`    | Key settings                                               |
    | --------- | ----------- | ---------------------------------------------------------- |
    | Medium    | `medium`    | `title`, `subtitle`, `canonical`, `publication`, `tags`    |
    | Dev.to    | `devto`     | `title`, `main_image`, `canonical`, `organization`, `tags` |
    | Hashnode  | `hashnode`  | `title`, `subtitle`, `main_image`, `publication`, `tags`   |
    | WordPress | `wordpress` | `title`, `main_image`, `type`                              |
    | Tumblr    | `tumblr`    | `title`, `link`, `sourceUrl`, `tags` (all optional)        |
  </Tab>

  <Tab title="Business">
    | Platform           | `__type`   | Key settings                                                           |
    | ------------------ | ---------- | ---------------------------------------------------------------------- |
    | Google My Business | `gmb`      | `topicType`, `callToActionType`, `callToActionUrl`, event/offer fields |
    | Listmonk           | `listmonk` | `subject`, `preview`, `list`, `template`                               |
  </Tab>

  <Tab title="Streaming">
    | Platform | `__type` | Key settings                       |
    | -------- | -------- | ---------------------------------- |
    | Twitch   | `twitch` | `messageType`, `announcementColor` |
  </Tab>
</Tabs>

### Platforms without custom settings

These platforms only require `{ "__type": "platform-name" }`:

| Platform | `__type`   |
| -------- | ---------- |
| Threads  | `threads`  |
| Mastodon | `mastodon` |
| Bluesky  | `bluesky`  |
| Telegram | `telegram` |
| Nostr    | `nostr`    |
| VK       | `vk`       |
| Kick     | `kick`     |

<Card title="View Provider Settings Reference" icon="code" href="/public-api/providers/x">
  Every setting each platform accepts, with a worked example.
</Card>

## Quick Examples

### Schedule a post to X (Twitter)

```json theme={"system"}
{
  "type": "schedule",
  "date": "2024-12-14T10:00:00.000Z",
  "shortLink": false,
  "tags": [],
  "posts": [
    {
      "integration": { "id": "your-integration-id" },
      "value": [
        {
          "content": "Hello from the PostQueen API! 🚀",
          "image": []
        }
      ],
      "settings": {
        "__type": "x",
        "who_can_reply_post": "everyone"
      }
    }
  ]
}
```

### Post immediately to LinkedIn

```json theme={"system"}
{
  "type": "now",
  "date": "2024-12-14T10:00:00.000Z",
  "shortLink": false,
  "tags": [],
  "posts": [
    {
      "integration": { "id": "your-linkedin-id" },
      "value": [
        {
          "content": "Exciting announcement! 🎉",
          "image": []
        }
      ],
      "settings": {
        "__type": "linkedin"
      }
    }
  ]
}
```

### Upload an image and post to Instagram

```bash theme={"system"}
# Step 1: Upload the image
curl -X POST "https://api.postqueen.ai/public/v1/upload" \
  -H "Authorization: your-api-key" \
  -F "file=@photo.jpg"

# Response: { "id": "img-123", "path": "https://uploads.postqueen.ai/photo.jpg", ... }

# Step 2: Create the post with the uploaded image
curl -X POST "https://api.postqueen.ai/public/v1/posts" \
  -H "Authorization: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "schedule",
    "date": "2024-12-14T10:00:00.000Z",
    "shortLink": false,
    "tags": [],
    "posts": [{
      "integration": { "id": "your-instagram-id" },
      "value": [{
        "content": "Beautiful sunset 🌅 #photography",
        "image": [{ "id": "img-123", "path": "https://uploads.postqueen.ai/photo.jpg" }]
      }],
      "settings": {
        "__type": "instagram",
        "post_type": "post"
      }
    }]
  }'
```

### Publish a Medium article

```json theme={"system"}
{
  "type": "now",
  "date": "2024-12-14T10:00:00.000Z",
  "shortLink": false,
  "tags": [],
  "posts": [
    {
      "integration": { "id": "your-medium-id" },
      "value": [
        {
          "content": "# Introduction\n\nThis is my article in markdown...",
          "image": []
        }
      ],
      "settings": {
        "__type": "medium",
        "title": "My Amazing Article",
        "subtitle": "A deep dive into something interesting",
        "tags": [
          { "value": "programming", "label": "Programming" }
        ]
      }
    }
  ]
}
```

### Create a Google My Business offer

```json theme={"system"}
{
  "type": "schedule",
  "date": "2024-12-14T10:00:00.000Z",
  "shortLink": false,
  "tags": [],
  "posts": [
    {
      "integration": { "id": "your-gmb-id" },
      "value": [
        {
          "content": "🎉 Holiday Sale! 20% off everything!",
          "image": []
        }
      ],
      "settings": {
        "__type": "gmb",
        "topicType": "OFFER",
        "callToActionType": "GET_OFFER",
        "callToActionUrl": "https://example.com/sale",
        "offerCouponCode": "HOLIDAY20"
      }
    }
  ]
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Schedule your first post" icon="paper-plane" href="/public-api/posts/create">
    The create endpoint, field by field, with a worked request.
  </Card>

  <Card title="Find your channel IDs" icon="plug" href="/public-api/integrations/list">
    Every connected channel, with the id every other call needs.
  </Card>

  <Card title="Use the typed client" icon="cube" href="/public-api/sdk">
    `@postqueen/node`, where each method maps to one endpoint.
  </Card>

  <Card title="Build for other people" icon="key" href="/public-api/oauth">
    OAuth2 tokens that act on your users' behalf, so nobody hands over a key.
  </Card>
</CardGroup>
