# Migrate from the Postiz public API to the OmniSocials API

> Index of all OmniSocials docs pages: https://docs.omnisocials.com/llms.txt

This guide is for developers who post through the Postiz public API, on Postiz Cloud or a self-hosted instance, and want to move to the OmniSocials API. In Postiz, one request carries a separate entry for each channel, with its own text, media, and settings. In OmniSocials, one post lists its channels in `accounts`, and per-channel text and options sit in objects keyed by channel ID. Scheduling moves from a `type` field to `schedule_at` and `publish_now`. Social accounts are connected in the OmniSocials dashboard. Postiz facts on this page come from docs.postiz.com and were checked on October 21, 2026.

## Concepts

| Postiz | OmniSocials | Notes |
|--------|-------------|-------|
| API key (`Authorization: your-api-key`, no `Bearer`) | API key (`Authorization: Bearer omsk_live_...`) | |
| Organization (the key reaches all its channels) | Workspace (the key reaches one workspace) | Create one key per workspace. |
| Customer (`GET /groups`) | Workspace | Give each customer its own workspace and key. |
| Integration (a channel) with an `id` | Account with an `id`, or the bare channel ID | A workspace has one account per channel ID. |
| Connect URL (`GET /social/{integration}`) | Dashboard connection | Accounts are connected only in the dashboard, never through the API. |
| One `posts[]` entry per integration | One post with an `accounts` array | One post `id` covers every channel. |
| Extra `value[]` entries (thread or comments) | `thread_parts` on X, Bluesky, Mastodon, and Threads, or `first_comment` | See [Threads and comments](#threads-and-comments). |
| `settings.__type` and its fields | A top-level object named after the channel | For example `x`, `pinterest`, `youtube`. |
| `type`: `draft`, `schedule`, `now` | No `schedule_at`, `schedule_at`, `publish_now: true` | |
| States `QUEUE`, `PUBLISHED`, `ERROR`, `DRAFT` | `scheduled`, `published` (or `warning`), `failed`, `draft` | `warning` means some channels published and some failed. |
| Tags | None | |
| Find slot | `GET /analytics/best-times` | Recommended slots from your own history. You pick the time. |

## Authentication

```text
Postiz:      Authorization: your-api-key
OmniSocials: Authorization: Bearer omsk_live_...
```

Postiz takes the raw key in the header. OmniSocials needs the `Bearer` prefix. A Postiz key reaches every channel of the organization. An OmniSocials key is bound to one workspace, chosen when you create the key under **Settings → API**. `GET /accounts` returns `workspace_id` and `workspace_name`, so you can check which workspace a key belongs to.

Keys have scopes, such as `posts:write` and `media:write`. A key's scopes cannot be edited later. See [Authentication](https://docs.omnisocials.com/authentication.md).

## Endpoint mapping

OmniSocials paths are relative to `https://api.omnisocials.com/v1`. Postiz paths are relative to `https://api.postiz.com/public/v1` on Postiz Cloud, or `https://{your-domain}/api/public/v1` when self-hosted.

| Postiz | OmniSocials | Notes |
|--------|-------------|-------|
| `POST /posts` | `POST /posts/create` | `POST /posts` is an alias. `POST /posts/create-and-publish` publishes at once. |
| `GET /posts` | `GET /posts` | No date range. Filter with `status` and page with `limit` (max 100) and `offset`, newest first. |
| `DELETE /posts/{id}` | `DELETE /posts/{id}` | Removes the post from OmniSocials and cancels a scheduled publish. It does not delete a live post on the platform. |
| `DELETE /posts/group/{group}` | `DELETE /posts/{id}` | One post covers all its channels, so there is no group. |
| `PUT /posts/{id}/status` | `PATCH /posts/{id}` | Send `draft: true` to unschedule, or `draft: false` to schedule a draft that has a `schedule_at`. |
| `PUT /posts/{id}/settings` | `PATCH /posts/{id}` | Drafts and scheduled posts only. Any create field can change. |
| `GET /integrations` | `GET /accounts` | Returns the key's workspace and its accounts, with `status` and `needs_reconnect`. |
| `GET /is-connected`, `GET /me` | `GET /accounts` | A `200` means the key works. |
| `GET /groups` | None | Each customer becomes a workspace with its own key. |
| `GET /social/{integration}` | None | Connect accounts in the dashboard. |
| `DELETE /integrations/{id}` | None | Disconnect accounts in the dashboard. |
| `GET /find-slot/{id}` | `GET /analytics/best-times` | Recommendations, not the next free slot. |
| `POST /upload` | `POST /media/upload` | Multipart with a `file` field, up to 100 MB. |
| `POST /upload-from-url` | `POST /media/upload-from-url` | Same `{ "url": "..." }` body. Up to 1 GB. |
| `GET /media/{id}/status` | `GET /media/{id}` | Wait for `status: "ready"`. |
| `GET /media` | `GET /media` | Search with `search` and filter with `folder_id`. |
| `GET /analytics/{integration}` | `GET /analytics/accounts` | `GET /analytics/overview` gives workspace totals. |
| `GET /analytics/post/{postId}` | `GET /analytics/posts/{id}` | `GET /analytics/posts?ids=` takes up to 100 post IDs. |
| `GET /notifications` | None | Use webhooks or poll `GET /posts?status=failed`. |
| `POST /generate-video`, `POST /video/function`, clipping endpoints | None | |

## Create a post

The same post in both APIs: a tweet scheduled for 08:00 UTC on November 2, 2026, that everyone can reply to.

Postiz:

```bash
curl -X POST https://api.postiz.com/public/v1/posts \
  -H "Authorization: $POSTIZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "schedule",
    "date": "2026-11-02T08:00:00.000Z",
    "shortLink": false,
    "tags": [],
    "posts": [
      {
        "integration": { "id": "your-x-integration-id" },
        "value": [{ "content": "We shipped dark mode today.", "image": [] }],
        "settings": { "__type": "x", "who_can_reply_post": "everyone" }
      }
    ]
  }'
```

OmniSocials:

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "We shipped dark mode today.",
    "accounts": ["x"],
    "schedule_at": "2026-11-02T08:00:00Z"
  }'
```

Everyone can reply by default. To limit replies, set `x.reply_settings` to `following` or `mentionedUsers`.

To post to more channels, add them to `accounts`. Where Postiz needs a second `posts[]` entry for different text, OmniSocials uses one `content` object:

```json
{
  "content": {
    "default": "We shipped dark mode today.",
    "linkedin": "We shipped dark mode today. Here is what changed for teams."
  },
  "accounts": ["x", "linkedin", "bluesky"],
  "schedule_at": "2026-11-02T08:00:00Z"
}
```

The response is a `201` with one post under `data`, not an array per channel. After publishing, `published_urls` on `GET /posts/{id}` maps each channel to its live URL.

## Field mapping

| Postiz field | OmniSocials field | Notes |
|--------------|-------------------|-------|
| `type: "now"` | `publish_now: true` | Or call `POST /posts/create-and-publish`. |
| `type: "schedule"` with `date` | `schedule_at` | ISO 8601 in UTC. Alias: `scheduled_at`. |
| `type: "draft"` | Leave out `schedule_at` and `publish_now` | `draft: true` with `schedule_at` keeps the time on the calendar without publishing. |
| `posts[].integration.id` | `accounts` | Account `id` from `GET /accounts`, or the channel ID. Alias: `channels`. |
| `posts[].value[0].content` | `content`, or `content.<channel>` for one channel | |
| `posts[].value[0].image[]` | `media_ids` or `media_urls` | Use the `id` from an OmniSocials upload, or pass public URLs. Per-channel media is an object keyed by channel. |
| `image[].thumbnailTimestamp` | `video_cover` | `type: "frame"` with `thumb_offset` in milliseconds. |
| `posts[].value[1..]` | `x.thread_parts` and the like, or `first_comment` | See below. |
| `posts[].settings` | A top-level object named after the channel | |
| `settings.__type` | The channel ID | Rename `linkedin-page` to `linkedin_page` and `gmb` to `google_business`. |
| `who_can_reply_post` (X) | `x.reply_settings` | `following` or `mentionedUsers`. Leave it out for everyone. |
| `shortLink` | None | Links are posted as written. |
| `tags` | None | |
| `posts[].group` | None | |

## Threads and comments

In Postiz, extra entries in `value` become a thread or comments, depending on the platform. OmniSocials splits these two cases:

- **Threads** on X, Bluesky, Mastodon, and Threads use `thread_parts` under the channel key. Each part has `text` and optional `media_ids` or `media_urls`. A thread has 2 to 25 parts.
- **One first comment** on Instagram, Facebook, LinkedIn, YouTube, TikTok, and Telegram uses `first_comment` under the channel key. It is text only.

```json
{
  "content": "We shipped dark mode today.",
  "accounts": ["x", "instagram"],
  "media_urls": ["https://example.com/dark-mode.png"],
  "x": {
    "thread_parts": [
      { "text": "We shipped dark mode today." },
      { "text": "It follows your system setting by default." }
    ]
  },
  "instagram": { "first_comment": "Update the app to try it." }
}
```

Chains of more than one comment have no equivalent. See [X threads](https://docs.omnisocials.com/platforms/x.md#posting-threads) for the details.

## Media and scheduling

**Media.** The upload calls map one to one. `POST /media/upload` takes a multipart `file`, and `POST /media/upload-from-url` takes `{ "url": "..." }`. Both return the media record under `data`. Reference it on a post with `media_ids: ["<data.id>"]`. There is no `path` to send along. You can also skip uploads and pass public URLs in `media_urls`, up to 10 files of 100 MB each.

OmniSocials accepts JPEG, PNG, GIF, WebP, HEIC, MP4, MOV, AVI, and PDF. AVIF, BMP, and TIFF are not on that list, so convert them first. A PDF is turned into one image per page. On LinkedIn it publishes as a document. See [Media](https://docs.omnisocials.com/media.md).

**Scheduling.** Send `schedule_at` as an ISO 8601 time in UTC. OmniSocials publishes within one minute of that time. A time in the past returns `400 validation_error`. To publish now, send `publish_now: true`.

## Webhooks

Postiz webhooks are set up in the app, under Settings, and fire when a post publishes. OmniSocials webhooks are registered through the API with `POST /webhooks`. The response returns a `secret` once. Store it.

```bash
curl -X POST https://api.omnisocials.com/v1/webhooks \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/omnisocials",
    "events": ["post.published", "post.failed"]
  }'
```

| Event | When it fires |
|-------|---------------|
| `post.scheduled` | A post is scheduled through the API |
| `post.published` | At least one channel published. A partial publish has `data.status` set to `warning` |
| `post.failed` | Every channel failed |
| `post.approved` | The last step of an approval workflow approved the post |
| `post.rejected` | An approver rejected the post |

Every delivery is signed. `X-OmniSocials-Signature: t=<timestamp>,v1=<hex>` is an HMAC-SHA256 of the timestamp, a dot, and the raw body, keyed with your webhook secret. Verify it before you trust the payload, and deduplicate on `X-OmniSocials-Delivery`. Answer with a `2xx` within 10 seconds. Failed deliveries are retried twice. See [Webhooks](https://docs.omnisocials.com/webhooks.md) for verification code.

## Platform coverage

As of October 21, 2026, from docs.postiz.com and the [OmniSocials platforms page](https://docs.omnisocials.com/platforms.md).

| Platform | Postiz | OmniSocials |
|----------|--------|-------------|
| Facebook | Yes (`facebook`) | Yes (`facebook`), Pages |
| Instagram | Yes (`instagram`, `instagram-standalone`) | Yes (`instagram`), Business and Creator accounts |
| Threads | Yes (`threads`) | Yes (`threads`) |
| LinkedIn | Yes, profile (`linkedin`) and page (`linkedin-page`) | Yes, profile (`linkedin`) and page (`linkedin_page`) |
| YouTube | Yes (`youtube`) | Shorts only (`youtube`) |
| TikTok | Yes (`tiktok`) | Yes (`tiktok`) |
| Pinterest | Yes (`pinterest`) | Yes (`pinterest`) |
| Bluesky | Yes (`bluesky`) | Yes (`bluesky`) |
| Mastodon | Yes (`mastodon`) | Yes (`mastodon`) |
| X | Yes (`x`) | Yes (`x`) |
| Google Business Profile | Yes (`gmb`) | Yes (`google_business`) |
| Telegram | Yes (`telegram`) | Yes (`telegram`), a channel or group |
| Reddit | Yes (`reddit`) | Coming soon |
| Snapchat | Not listed | Coming soon |
| Discord, Slack | Yes | No |
| Farcaster, Nostr, VK, Lemmy | Yes | No |
| Twitch, Kick | Yes | No |
| Dribbble, Medium, Dev.to, Hashnode, WordPress, Tumblr, MeWe | Yes | No |
| Listmonk, Moltbook, Skool, Whop | Yes | No |

## What is different

- **Hosted only.** OmniSocials has no self-hosted version.
- **Account connection.** OmniSocials has no connect-URL endpoint. Accounts are connected in the dashboard.
- **One key per workspace.** A Postiz key reaches the whole organization. An OmniSocials key reaches one workspace, so a multi-customer setup needs one key per customer.
- **One account per channel.** A workspace holds one account per channel ID. Two X accounts need two workspaces.
- **One post, many channels.** Per-channel text and settings live in objects keyed by channel, not in separate entries.
- **Threads and comments.** Threads work on X, Bluesky, Mastodon, and Threads. Other channels get one text-only first comment.
- **Rate limit.** OmniSocials allows 100 requests per minute per key, on every endpoint. Postiz limits only `POST /posts`, per organization and per hour. Its docs give 30 and 90 requests per hour on different pages, and self-hosted instances set the value with `API_LIMIT`. See [Rate limits and errors](https://docs.omnisocials.com/rate-limits-and-errors.md).
- **No tags, link shortening, video generation, or clipping.**
- **`GET /posts` has no date range.** Page through with `status`, `limit`, and `offset`.
- **X links.** X posts whose text contains a link use prepaid credits. Posts without a link do not. See [X](https://docs.omnisocials.com/platforms/x.md).
- **Fewer channels.** The channels in the last rows of the table above are not part of OmniSocials. YouTube is Shorts only, and Reddit is not live yet.
- **No test mode.** On OmniSocials, use drafts or a workspace with test accounts.
- **What OmniSocials adds.** Signed webhooks registered through the API, a [Social inbox](https://docs.omnisocials.com/inbox.md) API for DMs and comments, approval workflows, alt text per image, PDF to carousel, hashtag sets, Instagram comment-to-DM automations, and [SDKs](https://docs.omnisocials.com/sdks.md) in 8 languages.

## Migration steps

1. Create an OmniSocials account. Add one workspace per Postiz customer, or one for the whole organization if you use no customers.
2. Connect each social account in the dashboard, under **Settings → Organisation → Workspaces**.
3. Create one API key per workspace under **Settings → API**. Start with `posts:write`, `posts:read`, `media:write`, `accounts:read`, and `webhooks:manage`.
4. Call `GET /accounts` with each key. Map every Postiz integration `id` to an OmniSocials channel ID or account `id`.
5. Change the base URL and add `Bearer ` before the key.
6. Rewrite the post body. Merge the `posts[]` entries into one `accounts` array and one `content` object, and move `settings` to channel keys. Use the [field mapping](#field-mapping) table.
7. Re-upload media with `POST /media/upload` or `POST /media/upload-from-url`, and reference it with `media_ids`.
8. Register your webhooks with `POST /webhooks`, and add signature verification.
9. Recreate the posts that are still queued in Postiz with `schedule_at`. Then delete them in Postiz with `DELETE /posts/{id}`, so nothing publishes twice.
10. Test with drafts first, since there is no test mode. Then publish one post with `publish_now: true` and check `published_urls` on `GET /posts/{id}`.

:::tip
Posts without `schedule_at` or `publish_now` are drafts and never publish on their own. Use them to check your field mapping against real accounts.
:::
