Migrate from Zernio (Late) to the OmniSocials API
This guide is for developers who post through the Zernio API (formerly Late, getlate.dev) and want to move to the OmniSocials API. Most of the work is renaming fields. The create-post body is flatter, accounts are referenced by channel ID, scheduled times go in as UTC, and webhook signatures use a different header. The biggest change is that social accounts are connected in the OmniSocials dashboard, not through an API connect flow. Zernio facts on this page come from docs.zernio.com and were checked on October 21, 2026.
If your code still uses the Late names (LATE_API_KEY, the late Python package), the same steps apply.
Concepts
| Zernio | OmniSocials | Notes |
|---|---|---|
API key (sk_...) | API key (omsk_live_...) | Both go in Authorization: Bearer. An OmniSocials key belongs to one workspace. |
Restricted key (zrk_...) | Key scopes | Pick scopes such as posts:write or media:read when you create the key. See Authentication. |
| Profile | Workspace | A workspace is one set of connected accounts, usually one brand or client. Create workspaces in the dashboard. |
Account (_id) | Account (id, for example 368204_instagram) | You can also pass the bare channel ID, such as instagram. A workspace has one account per channel. |
Connect URL (GET /v1/connect/{platform}) | Dashboard connection | Accounts are connected only in the dashboard, never through the API. |
platforms[] on a post | accounts on a post | A flat array of account IDs or channel IDs. |
customContent per platform | content object keyed by channel | default is the fallback text. |
| Queue and slots | None | GET /analytics/best-times recommends slots. You pick the time. |
Post status publishing | posting | |
Post status partial | warning | Published on some channels. errors names the rest. |
| Webhook settings | Webhooks | Registered with POST /webhooks. |
Authentication
Both APIs use a Bearer token in the Authorization header.
Text
The difference is in what a key can reach. With Zernio, you pass a profileId to pick a profile. An OmniSocials key is bound to one workspace, which you choose when you create the key. If you serve several brands, create one workspace and one key per brand, and store the key next to the brand in your own database. GET /accounts returns workspace_id and workspace_name, so you can check which workspace a key belongs to.
Create keys under Settings → API in app.omnisocials.com. A key's scopes cannot be edited later. If you need more, create a new key.
Endpoint mapping
All OmniSocials paths are relative to https://api.omnisocials.com/v1. Zernio paths are relative to https://zernio.com/api.
| Zernio | OmniSocials | Notes |
|---|---|---|
POST /v1/posts | POST /posts/create | POST /posts is an alias. POST /posts/create-and-publish publishes at once. |
GET /v1/posts | GET /posts | Filter with status. Page with limit (max 100) and offset. |
GET /v1/posts/{postId} | GET /posts/{id} | |
PUT /v1/posts/{postId} | PATCH /posts/{id} | Drafts and scheduled posts only. Send only the fields you change. |
DELETE /v1/posts/{postId} | DELETE /posts/{id} | Removes the post from OmniSocials and cancels a scheduled publish. It does not delete a live post on the platform. |
POST /v1/posts/{postId}/retry | POST /posts/{id}/retry | Retries only the failed channels, up to 3 times per channel. |
POST /v1/posts/{postId}/unpublish | None | Delete live posts from the dashboard or in the platform's app. |
POST /v1/posts/{postId}/edit | None | Published posts cannot be changed. |
POST /v1/posts/bulk-upload | None | Call POST /posts/create once per post. |
POST /v1/media/presign | POST /media/create-upload-url | Or skip uploads and pass media_urls on the post. See below. |
GET /v1/profiles and profile CRUD | None | Manage workspaces in the dashboard. |
GET /v1/accounts | GET /accounts | Returns the key's workspace and its connected accounts. |
GET /v1/accounts/health | GET /accounts | Read status and needs_reconnect on each account. |
GET /v1/accounts/follower-stats | GET /analytics/accounts | |
GET /v1/connect/{platform} | None | Connect accounts in the dashboard. |
GET /v1/queue/slots, GET /v1/queue/next-slot | GET /analytics/best-times | Recommendations from your own history, not a queue. |
GET /v1/analytics | GET /analytics/overview, GET /analytics/posts/{id}, GET /analytics/posts?ids= | Bulk reads take up to 100 post IDs. |
POST /v1/webhooks/settings and CRUD | POST /webhooks, GET, PATCH, DELETE /webhooks/{id} | POST /webhooks/{id}/rotate-secret rotates the secret. |
GET /v1/webhooks/logs | GET /webhooks/{id} | Returns failure_count and last_failure. There is no full delivery log. |
POST /v1/webhooks/test | None | Subscribe to post.scheduled and schedule a post through the API. That sends a real event. |
POST /v1/tools/validate/post | POST /media/check | Checks media against your connected channels. There is no full post validation call. |
GET /v1/inbox/conversations, GET /v1/inbox/comments | GET /inbox/conversations | DMs, comments, and mentions in one list. Filter with type. |
POST /v1/inbox/conversations/{conversationId}/messages | POST /inbox/conversations/{conversationId}/reply | Needs the opt-in inbox:write scope. See Social inbox. |
Create a post
The same post in both APIs: one image to LinkedIn and Instagram, scheduled for 09:00 in Amsterdam on November 2, 2026.
Zernio:
Terminal
OmniSocials:
Terminal
OmniSocials has no timezone field, so the local time is converted to UTC first (Amsterdam is UTC+1 in November). The response is a 201 with the post under data and "status": "scheduled".
Per-platform text works the same way, but it lives in one content object instead of on each platform entry:
JSON
Field mapping
| Zernio field | OmniSocials field | Notes |
|---|---|---|
content | content | A string, or an object with default and channel keys. |
platforms[].platform and platforms[].accountId | accounts | Account id from GET /accounts, or the channel ID. Alias: channels. |
platforms[].customContent | content.linkedin, content.x, and so on | One key per channel ID. |
platforms[].customMedia | media_urls or media_ids as an object keyed by channel | An empty array opts a channel out of media. |
platforms[].platformSpecificData | A top-level object named after the channel | For example tiktok.privacy_level, pinterest.board_id, youtube.privacy_status. See Platforms. |
tiktokSettings | tiktok | |
facebookSettings | facebook | |
title | youtube.title, tiktok.title, pinterest.title | Titles are set per channel. |
mediaItems[].url | media_urls | Up to 10 URLs, 100 MB per file. |
mediaItems[].type | None | The type is read from the file. |
scheduledFor | schedule_at | ISO 8601 in UTC, for example 2026-11-02T08:00:00Z. Alias: scheduled_at. |
timezone | None | Convert to UTC before you send. |
publishNow | publish_now | |
isDraft | Leave out schedule_at and publish_now | A post without either is a draft. draft: true with schedule_at keeps the time on the calendar without publishing. |
queuedFromProfile, queueId | None | Choose the time yourself, or use GET /analytics/best-times. |
recycling | None | |
dryRun | None | |
Idempotency-Key header | None | Store the returned post id and check it before you retry a create. |
Media and scheduling
Media. Zernio uploads take three calls: presign, PUT the file, then use the publicUrl. OmniSocials offers three shorter routes:
media_urlson the post. Pass public URLs and OmniSocials downloads them. Up to 10 files, each up to 100 MB.POST /media/upload. A multipart upload with afilefield, up to 100 MB. Use the returneddata.idinmedia_ids.POST /media/upload-from-url. OmniSocials fetches a public URL of up to 1 GB. Files above about 100 MB come back withstatus: "processing". PollGET /media/{id}until it isready.
For a client that cannot send your API key, POST /media/create-upload-url returns a single-use upload_url that expires after 10 minutes. POST a multipart file to it with no auth header.
The largest file OmniSocials takes is 1 GB, through upload-from-url. Zernio's presign limit is 5 GB. A PDF is turned into one image per page. On LinkedIn it publishes as a document. See Media.
Scheduling. Send schedule_at as an ISO 8601 time in UTC. OmniSocials publishes within one minute of that time. Two behaviors differ from Zernio:
- A time in the past returns
400 validation_error. Zernio publishes a pastscheduledForat once. To publish now, sendpublish_now: true. - There is no queue. Store your own slots, or read
GET /analytics/best-times?platform=instagramfor recommended times.
Webhooks
Register a webhook with POST /webhooks and a list of events. The response returns a secret once. Store it.
Terminal
| Zernio event | OmniSocials event | Notes |
|---|---|---|
post.scheduled | post.scheduled | Sent only for posts scheduled through the API. |
post.published | post.published | Fires when at least one channel succeeds. |
post.partial | post.published | data.status is warning. Check each entry in data.targets. |
post.failed | post.failed | Every channel failed. |
post.platform.published, post.platform.failed | None | Read the per-channel results in data.targets. |
post.cancelled, post.recycled, post.platform.deleted, post.external.* | None | |
account.connected, account.disconnected | None | Poll GET /accounts and check needs_reconnect. |
message.received, comment.received | None | Poll GET /inbox/conversations or GET /inbox/next. |
| None | post.approved, post.rejected | Approval workflow outcomes. |
Signatures. Zernio sends X-Zernio-Signature, an HMAC-SHA256 of the raw body. OmniSocials sends X-OmniSocials-Signature: t=<timestamp>,v1=<hex>. The HMAC-SHA256 covers the timestamp, a dot, and the raw body, keyed with your webhook secret. Deduplicate on X-OmniSocials-Delivery. See Webhooks for verification code.
Delivery. Answer with a 2xx within 10 seconds. Failed deliveries are retried twice, after 2 seconds and then 5 more. After that the event is dropped. Webhook URLs must use HTTPS.
Platform coverage
As of October 21, 2026, from docs.zernio.com and the OmniSocials platforms page.
| Platform | Zernio | OmniSocials |
|---|---|---|
Yes (facebook) | Yes (facebook), Pages | |
Yes (instagram) | Yes (instagram), Business and Creator accounts | |
| Threads | Yes (threads) | Yes (threads) |
Yes (linkedin) | Yes, profile (linkedin) and company page (linkedin_page) | |
| YouTube | Yes (youtube) | Shorts only (youtube) |
| TikTok | Yes (tiktok) | Yes (tiktok) |
Yes (pinterest) | Yes (pinterest) | |
| Bluesky | Yes (bluesky) | Yes (bluesky) |
| X | Yes (twitter) | Yes (x) |
| Google Business Profile | Yes (googlebusiness) | Yes (google_business) |
| Telegram | Yes, as a messaging channel (telegram) | Yes (telegram), a channel or group |
| Mastodon | Not listed | Yes (mastodon) |
Yes (reddit) | Coming soon | |
| Snapchat | Closed beta (snapchat) | Coming soon |
| WhatsApp, Discord, Slack | Yes, as messaging channels | No |
What is different
- Account connection. OmniSocials has no connect-URL endpoint. Accounts are connected in the dashboard. If your product lets end users connect their own accounts through your app, OmniSocials does not support that today.
- No profiles API. Workspaces are created in the dashboard, and each API key reaches one workspace.
- One account per channel. A workspace holds one account per channel ID. Two Instagram accounts need two workspaces.
- Rate limit. OmniSocials allows 100 requests per minute per key. Zernio's documented limit runs from 60 to 1,200 requests per minute, by the number of connected accounts. Limits are per key on OmniSocials, so you can split heavy work across keys. See Rate limits and errors.
- Fewer post operations. There is no queue, recycling, bulk upload, idempotency key, dry run, edit of a published post, or unpublish through the API.
- Times are UTC. There is no
timezonefield, and past times are refused. - X links. X posts whose text contains a link use prepaid credits. Posts without a link do not. See X.
- Fewer webhook events. There are no account, inbox, or per-platform events, no test endpoint, and no full delivery log.
- Error shape. Zernio errors are flat, with
error,type, andcodeat the top level. OmniSocials errors nest undererror, withcode,message, and optionaldetails. Branch onerror.code. - No messaging or ads channels. WhatsApp, Discord, Slack, SMS, and ads APIs are not part of OmniSocials. YouTube is Shorts only, and Reddit is not live yet.
- No test mode. Neither API documents a general sandbox. On OmniSocials, use drafts or a workspace with test accounts.
- What OmniSocials adds. Approval workflows with
post.approvedandpost.rejectedevents, alt text per image, PDF to carousel and LinkedIn documents, automatic first comments, hashtag sets, Instagram comment-to-DM automations, and Mastodon.
Migration steps
- Create an OmniSocials account. Add one workspace for each Zernio profile.
- Connect each social account in the dashboard, under Settings → Organisation → Workspaces.
- Create one API key per workspace under Settings → API. Start with
posts:write,posts:read,media:write,accounts:read, andwebhooks:manage. - Call
GET /accountswith each key. Map every ZernioaccountIdto an OmniSocials channel ID or accountid. - Change the base URL and the key. Rename the post fields with the field mapping table. Convert
scheduledForandtimezoneto a UTCschedule_at. - Replace the presign upload with
media_urls, or withPOST /media/uploadandmedia_ids. - Register your webhooks with
POST /webhooks. Swap the signature check and map the events. - Recreate the posts that are still scheduled in Zernio with
schedule_at. Then delete them in Zernio withDELETE /v1/posts/{postId}, so nothing publishes twice. - Test with drafts first, since there is no test mode. Then publish one post with
publish_now: trueand checkpublished_urlsonGET /posts/{id}. - Handle
429responses. Wait for theRetry-Afterheader, then retry.
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.