# PostZen - Overview: Start here to learn about PostZen. - [Quickstart](/): Get started with the PostZen API — authenticate, connect accounts, and schedule your first post in minutes. - [SDKs](/sdks): Official PostZen API client libraries for Node.js and Python. Post across 9 platforms. - [MCP server](/mcp): Connect Claude and other AI assistants to the full PostZen API through the Model Context Protocol. - [CLI](/cli): Drive the PostZen API from your terminal or scripts. JSON output by default, built for piping and AI agents. - [Inbox](/inbox): Read and reply to comments on Instagram, Facebook, and Threads, and manage Instagram direct messages, from the dashboard or the API. - [Comment automations](/comment-automations): Turn Instagram comments and story replies into automatic DMs. Keyword matching, private replies, optional public replies, and a log of every trigger. - [Contacts](/contacts): The people your connected accounts talk to. Filled in automatically from the inbox and comment automations, with tags, notes, custom fields, subscription and block flags, and a bulk import. - [Webhooks](/webhooks): Receive post outcomes, account health, analytics, inbox, and contact events from PostZen in real time. - [Billing](/billing): How PostZen plans, extra-account usage, billing thresholds, and X API pass-through work. - Platforms: Platform-specific guides for every network PostZen publishes to. - [Instagram](/platforms/instagram): Connect an Instagram professional account and publish or schedule feed photos, carousels, reels, and stories with PostZen. - [X (Twitter)](/platforms/twitter): Connect an X account and publish text, images, video, or multi-post threads with the PostZen API. - [Facebook](/platforms/facebook): Publish text, image, video, and link posts to Facebook Pages with PostZen. - [LinkedIn](/platforms/linkedin): Publish and schedule LinkedIn text, image, video, document, and reshare posts to a personal profile with PostZen. Company-page posting is coming soon, pending LinkedIn's approval. - [TikTok](/platforms/tiktok): Connect TikTok to PostZen and publish or schedule video and photo posts. - [YouTube](/platforms/youtube): Connect a YouTube account and upload videos through the PostZen API. - [Pinterest](/platforms/pinterest): Publish and schedule image, carousel, and video pins to Pinterest boards with PostZen. - [Bluesky](/platforms/bluesky): Connect Bluesky with an app password, then publish or schedule text and image posts through PostZen. - [Threads](/platforms/threads): Connect a Threads account and publish text, image, video, carousel, and multi-post thread posts with the PostZen API. - [Telegram](/platforms/telegram): Connect a Telegram channel or group with an access code, then publish or schedule text, photo, video, and album posts through PostZen. - API Reference: Generated from the PostZen OpenAPI spec. - [API Reference](/api-reference): Generated from the PostZen OpenAPI spec. - Profiles: List, create, update, and delete PostZen profiles. - [List profiles](/api-reference/profiles/list-profiles): Returns profiles available to the API key, sorted by creation date (oldest first). Read-only and read-write API keys are accepted. - [Create a profile](/api-reference/profiles/create-profile): Creates a profile. This endpoint requires a read-write API key with access to all profiles. - [Get a profile](/api-reference/profiles/get-profile): Returns a single profile visible to the API key. - [Update a profile](/api-reference/profiles/update-profile): Updates one or more profile fields. This endpoint requires a read-write API key. - [Delete a profile](/api-reference/profiles/delete-profile): Deletes a profile. The default profile cannot be deleted, and profiles with connected accounts must be disconnected first. - Accounts: List and disconnect connected social accounts. - [List accounts](/api-reference/accounts/list-accounts): Returns connected social accounts available to the API key, sorted by connection date (newest first). Read-only and read-write API keys are accepted. - [Disconnect an account](/api-reference/accounts/disconnect-account): Disconnects and removes a connected social account. Pending scheduled, queued, and publishing targets for that account are canceled. This endpoint requires a read-write API key. - [List Pinterest boards](/api-reference/accounts/get-pinterest-boards): Lists boards owned by a connected Pinterest account. Read-only and read-write API keys are accepted. - [Create Pinterest board](/api-reference/accounts/create-pinterest-board): Creates a board for the connected Pinterest account. This endpoint requires a read-write API key. - [Set default Pinterest board](/api-reference/accounts/update-pinterest-boards): Verifies a Pinterest board and stores it as the account default used when a pin target omits `settings.boardId`. This endpoint requires a read-write API key. - Connect: Start and complete OAuth account connection flows. - [List Pinterest boards for the connect flow](/api-reference/connect/list-pinterest-boards-for-selection): Lists boards after Pinterest OAuth completes so the client can show a board picker. The state handle remains valid for board selection for 30 minutes from connect-flow creation. This endpoint requires a read-write API key. - [Select Pinterest board](/api-reference/connect/select-pinterest-board): Verifies and stores the default board for the Pinterest account created by the connect session. This endpoint requires a read-write API key. - [Create an OAuth connect URL](/api-reference/connect/create-connect-url): Initiates an OAuth connection flow and returns an authorization URL to redirect the user to. The OAuth state expires after 10 minutes. This endpoint requires a read-write API key. For `bluesky`, the returned `authUrl` is a PostZen-hosted page where the user enters their Bluesky handle and app password (no developer app or OAuth redirect is required); the connection completes when they submit that form. For `telegram`, the returned `authUrl` is a PostZen-hosted page that issues a short-lived access code (there is no OAuth grant and no Telegram authorization screen); the connection completes when the user adds @PostZenScheduleBot as an administrator of a channel or group and sends that code to the bot. To reauthorize an existing account, pass `accountId` instead of `profileId`. The flow is pinned to that account: the user must authorize the same provider account, and the account keeps its id, profile, scheduled posts and comment automations. Reauthorizing an account whose status is `connected` or `needsReauth` does not count against your plan's included accounts. Disconnecting and connecting again instead creates a new account id and cancels the account's pending posts, so prefer `accountId`. - [Complete an OAuth connection](/api-reference/connect/complete-connect): Exchanges an OAuth authorization code for tokens and connects the account to the specified profile. This endpoint requires a read-write API key. - Media: Create presigned upload URLs for PostZen-hosted media, or upload a small file directly. - [Create a presigned media upload URL](/api-reference/media/create-media-presign): Creates a presigned URL for uploading an image, video, GIF, or PDF to PostZen-hosted storage. Upload the file with an HTTP `PUT` to `uploadUrl`, then reference `publicUrl` in post `mediaItems`. This endpoint requires a read-write API key. - [Upload a file directly](/api-reference/media/upload-media-direct): Uploads a file in the request itself and returns a public URL, for cases where a two-step presign is inconvenient and the file is small — chiefly direct message attachments, where Meta fetches the URL itself. The stored object is deleted automatically after seven days, so it is not a substitute for `POST /v1/media/presign` when publishing posts. Maximum 25MB. Requires a read-write API key. - Posts: Create drafts, scheduled posts, or immediate posts, and read LinkedIn comments and reactions on published organization posts. The LinkedIn reads arrive with company-page posting, which is not available yet, pending LinkedIn's approval of PostZen's Community Management API access. - [Bulk upload posts from CSV](/api-reference/posts/bulk-upload-posts): Validates and creates up to 500 posts from a CSV file. This endpoint requires a read-write API key. Each data row targets every connected account for the selected platforms on one profile. Set `dryRun=true` to validate without checking the Free plan's monthly post allowance, ingesting media, or writing data. A non-dry-run upload is rejected with HTTP 402 before creating any rows if its publishable targets would exceed that allowance. A mixed success/failure response uses HTTP 207; all-success and all-failure responses use HTTP 200. | CSV column | Purpose | | --- | --- | | `post_content` | Required base post text. | | `platforms` | Required comma-separated platforms: `instagram`, `facebook`, `threads`, `tiktok`, `linkedin`, `x`, `youtube`, `pinterest`, `bluesky`, or `telegram`; `twitter` aliases `x`. | | `profiles` | Required single profile id or unique profile name. | | `schedule_time` | Required column. Use `YYYY-MM-DD HH:mm[:ss]` in `tz`, or ISO 8601 with an explicit offset. May be empty for draft, publish-now, or queue rows. | | `tz` | IANA timezone; defaults to `UTC`. | | `media_urls` | Comma-separated HTTP(S) URLs, up to 10 per row and 50 distinct URLs per upload. | | `is_draft`, `publish_now`, `use_queue` | Mutually exclusive boolean mode flags. With none set, the row is scheduled. | | `title`, `tags`, `hashtags`, `mentions`, `visibility` | General title, comma-separated tags, appended hashtags/mentions, and YouTube visibility. | | `custom_content_` | Platform-specific content override; `custom_content_twitter` targets X. | | `youtube_title`, `youtube_description` | YouTube title and description override. | | `facebook_first_comment`, `linkedin_first_comment` | First comments. | | `instagram_content_type`, `instagram_collaborators`, `instagram_first_comment` | Instagram post type, collaborators, and first comment. | | `tiktok_privacy`, `tiktok_allow_comments`, `tiktok_allow_duet`, `tiktok_allow_stitch`, `tiktok_brand_partner`, `tiktok_organic_brand`, `tiktok_ai_generated`, `tiktok_draft`, `tiktok_description` | TikTok publishing options. | | `telegram_parse_mode`, `telegram_disable_web_page_preview`, `telegram_disable_notification`, `telegram_protect_content` | Telegram publishing options. | | `pinterest_title`, `pinterest_link`, `pinterest_board_id`, `pinterest_cover_image_url`, `pinterest_cover_image_key_frame_time` | Pinterest pin options, including alternative video cover selections. | - [List posts](/api-reference/posts/list-posts): Returns posts the API key can access, newest first. A post's profiles are the profiles of its target accounts, and a key limited to selected profiles only sees posts whose accounts all sit in profiles it can access. Read-only and read-write API keys are accepted. Published posts include a `platformPostId` and `platformPostUrl` for each published platform target. Results are capped to the 1000 most recent matching posts. - [Create a post](/api-reference/posts/create-post): Creates a draft, scheduled post, immediate post, or queued post. This endpoint requires a read-write API key. Provide exactly one creation mode: `publishNow`, `scheduledFor`, `isDraft`, or `queuedFromProfile` (optionally with `queueId`), which claims the next free slot in that profile's queue and returns it as `scheduledFor`. The accounts in `platforms` can belong to different profiles; an API key limited to selected profiles must have access to the profile of every account. - [Get a post](/api-reference/posts/get-post): Returns one post when the API key can access the profile of every account the post targets. Missing and inaccessible posts both return 404. - [Update a post](/api-reference/posts/update-post): Updates an editable post. Every body field is optional; omitted fields keep their current values, and omitting all timing fields keeps the current scheduling and draft status. At most one of `publishNow`, `scheduledFor`, `isDraft`, or `queuedFromProfile` may select a new timing mode. Accounts in `platforms` can belong to different profiles; the API key needs access to all of them. - [Delete a post](/api-reference/posts/delete-post): Deletes a draft, scheduled, queued, failed, partially failed, or canceled post. Published and publishing posts cannot be deleted. - [List comments on a LinkedIn post](/api-reference/posts/list-post-comments): Returns the comments LinkedIn holds for a post published through PostZen. LinkedIn only, and organization (company page) posts only. The post's LinkedIn connection must hold `r_organization_social_feed`, which LinkedIn ships with its Community Management API product. Not available yet: company-page posting is waiting on LinkedIn's approval of PostZen's Community Management API access, and until then this endpoint returns `403 orgScopesDisabled`. A personal (member) post always returns `403 personalPostUnsupported`: reading a member's own comments and reactions needs `r_member_social_feed`, which LinkedIn grants to select developers only. Read-only and read-write API keys are accepted. - [List reactions on a LinkedIn post](/api-reference/posts/list-post-reactions): Returns the individual reactions LinkedIn holds for a post published through PostZen, plus a per-type count of the returned page. LinkedIn only, and organization (company page) posts only. The post's LinkedIn connection must hold `r_organization_social_feed`, which LinkedIn ships with its Community Management API product. Not available yet: company-page posting is waiting on LinkedIn's approval of PostZen's Community Management API access, and until then this endpoint returns `403 orgScopesDisabled`. A personal (member) post always returns `403 personalPostUnsupported`: reading a member's own comments and reactions needs `r_member_social_feed`, which LinkedIn grants to select developers only. Read-only and read-write API keys are accepted. - Inbox: Read and moderate comments on Instagram, Facebook, and Threads posts. Every endpoint takes the connected account as `accountId` and identifies the post either by its PostZen post id or by the platform's own post/media id. Reads also refresh PostZen's stored copy of the thread. An account on any other platform returns `400 platformUnsupported`, and the write endpoints need the account's comment-management scope — reconnect the account when they answer `403 platformCapabilityMissing`. The conversation endpoints cover Instagram direct messages instead: they read PostZen’s synced copy of each thread, refresh it from the platform as a side effect, and keep read state and archive state locally because Meta exposes neither. Sending is bounded by Meta’s 24-hour reply window, which surfaces as `400 PLATFORM_LIMITATION`. - [List comments on a post](/api-reference/inbox/list-inbox-post-comments): Returns the comments the platform holds for one Instagram, Facebook, or Threads post, and refreshes PostZen's stored copy of the thread as a side effect. `replies` carries the first page of nested replies when the platform returns them cheaply — `repliesHasMore` says when it did not, and passing that comment's id as `commentId` pages through the rest. Instagram threads are only two levels deep, so a reply never carries replies of its own. Fields a platform does not report are absent rather than zero: Threads, for example, never reports per-reply like counts. Read-only and read-write API keys are accepted. - [Comment on a post or reply to a comment](/api-reference/inbox/reply-to-inbox-post): Publishes a comment on the post, or a reply to `commentId` when one is given. Requires a read-write API key and the account's comment-management scope; without the scope the call answers `403 platformCapabilityMissing` and nothing is sent to the platform. `attachmentUrl` is Facebook only — every other platform rejects it with `400 attachmentUnsupported`. Instagram is two levels deep, so replying to a reply is redirected to that reply's top-level parent. A Threads reply is created as a container and then published; when the publish is still pending after PostZen's internal retries the call returns `502 publishPending` and the reply may still appear on its own, so check the thread before retrying rather than posting twice. - [Delete a comment](/api-reference/inbox/delete-inbox-comment): Deletes a comment. Requires a read-write API key and the account's comment-management scope. `canDelete` on the comment says whether the platform allows it: Facebook reports it per comment, and Threads only allows deleting replies the connected account itself wrote. - [Hide a comment](/api-reference/inbox/hide-inbox-comment): Hides a comment so only its author still sees it. Requires a read-write API key and the account's comment-management scope. `canHide` on the comment says whether the platform allows it: Facebook reports it per comment, and Threads only supports hiding top-level replies on the connected account's own posts — hiding one there also hides the replies beneath it. - [Unhide a comment](/api-reference/inbox/unhide-inbox-comment): Restores a hidden comment. Requires a read-write API key and the account's comment-management scope. On Threads, unhiding a top-level reply also restores the replies beneath it. - [List direct message conversations](/api-reference/inbox/list-inbox-conversations): Returns the Instagram direct message threads PostZen holds for the accounts this key can reach, newest activity first, and refreshes that stored copy from the platform as a side effect (throttled per account, so a tight polling loop costs nothing extra). `meta` reports which accounts could not be refreshed; their stored threads are still returned. Read-only and read-write API keys are accepted. - [Search direct message conversations](/api-reference/inbox/search-inbox-conversations): Searches the conversations PostZen has already synced — Meta exposes no message search API, so this reads PostZen's own copy and refreshes it first. Message text matches whole tokens, case- and accent-insensitively (`cafe` finds `café`, `art` does not match `start`); participant names and usernames match on substring. Setting `direction` narrows the search to message text only, so a thread that matched only on the participant's name is excluded. Read-only and read-write API keys are accepted. - [Get a direct message conversation](/api-reference/inbox/get-inbox-conversation): Returns one thread, refreshing PostZen’s stored copy from the platform first. Read-only and read-write API keys are accepted. - [Archive or unarchive a conversation](/api-reference/inbox/update-inbox-conversation): Sets the thread’s status. This is PostZen-local state — Meta has no archive API, so nothing is sent to the platform and a later sync never overwrites it. Requires a read-write API key. - [Mark a conversation read](/api-reference/inbox/mark-inbox-conversation-read): Moves the thread’s local read watermark to its newest synced message and returns how many incoming messages crossed from unread to read. Nothing is sent to the platform, and reading messages never does this implicitly. Requires a read-write API key. - [List messages in a conversation](/api-reference/inbox/list-inbox-conversation-messages): Returns the messages PostZen holds for one thread, oldest first by default, refreshing them from the platform when the stored copy is stale. Strictly read-only: it never marks the thread read — use `POST /v1/inbox/conversations/{conversationId}/read` for that. Meta returns full content only for roughly the twenty newest Instagram messages, so older ones may carry an id and timestamp without text or attachments. Read-only and read-write API keys are accepted. - [Send a direct message](/api-reference/inbox/send-inbox-message): Sends a message into an existing thread. The recipient is the other participant on that thread, so no recipient id is needed. Exactly one of `message` or `attachmentUrl` may be sent per call — Meta's Send API carries one payload at a time, so combining them is a 400. Meta only accepts replies within 24 hours of the person's last message; outside that window the platform refuses the send and PostZen reports `400 PLATFORM_LIMITATION` with Meta's own envelope attached. Sends are hard-gated on the account's messaging scope. Requires a read-write API key. - Queues: Manage per-profile posting queues. A queue is a named weekly schedule (`slots` of `{ dayOfWeek, time }`) evaluated in the queue's own timezone. A profile may hold several queues and exactly one is the default. Creating a post with `queuedFromProfile` claims the next free slot inside a single transaction, which is what keeps concurrent creates from landing on the same instant. - [Get a queue schedule](/api-reference/queues/list-queue-slots): Returns the queue selected by `queueId`, or the profile's default queue when `queueId` is omitted. With `all=true` every queue on the profile is returned instead. `nextSlots` holds the next five instants the queue would hand out, already skipping occupied slots; a paused or slotless queue returns an empty array. - [Create a queue](/api-reference/queues/create-queue-slot): Creates a posting queue on a profile. A profile may hold up to 10 queues, each with up to 56 slots. The first queue on a profile automatically becomes its default. This endpoint requires a read-write API key. - [Update a queue](/api-reference/queues/update-queue-slot): Replaces a queue's timezone and slots, and optionally renames, pauses, or promotes it to default. With `reshuffleExisting: true` the queue's future scheduled posts are re-placed onto the new schedule in their existing order; posts that are already publishing, published, or failed are never moved, and at most 200 posts may move in one call. Validation and slot assignment both run before the first write, so a rejected update leaves the queue untouched. This endpoint requires a read-write API key. - [Delete a queue](/api-reference/queues/delete-queue-slot): Deletes the queue named by `queueId`, or every queue on the profile when `queueId` is omitted. Posts already placed by the queue keep their scheduled times; only the queue link is removed. Deleting the default queue promotes the profile's oldest remaining queue. This endpoint requires a read-write API key. - [Get the next queue slot](/api-reference/queues/get-next-queue-slot): Returns the next free instant the queue would hand out. This is a preview only — the slot is not reserved. Create queued posts with `queuedFromProfile` rather than passing this value back as `scheduledFor`, or a concurrent create can take the slot first. - [Preview upcoming queue slots](/api-reference/queues/preview-queue): Returns the next `count` instants the queue would hand out, in ascending order. Occupied slots are skipped, so the preview matches what the next posts would actually receive. Previewing reserves nothing. - Analytics: Read post, engagement, follower, and posting-time analytics, and synchronize external posts. - [Get post analytics](/api-reference/analytics/get-analytics): Returns one post when `postId` is supplied. Otherwise returns a paginated analytics list with aggregate overview metrics. - [Get a post analytics timeline](/api-reference/analytics/get-post-timeline): Returns one daily row per platform for a PostZen post, imported external post, or platform post id. - [Get daily analytics metrics](/api-reference/analytics/get-daily-metrics): Returns daily aggregate metrics and a per-platform breakdown. Publish attribution assigns lifetime metrics to the post's publish date; received attribution assigns metric deltas to the day they were observed. - [Get the best times to post](/api-reference/analytics/get-best-time-to-post): Returns historical engagement slots grouped by UTC day of week and hour, ordered by average engagement descending. Hours are UTC; day_of_week uses 0 = Sunday through 6 = Saturday. - [Get follower statistics](/api-reference/analytics/get-follower-stats): Returns follower history and growth for connected accounts at daily, weekly, or monthly granularity. - [Synchronize external posts](/api-reference/analytics/sync-external-posts): Fetches an account's latest posts published directly on its platform. Supplying `url` or `postId` searches for a specific post. Requests made within the per-account debounce window return cached results with `synced.skipped` set to true. - API Keys: Manage PostZen API keys programmatically. Creating and deleting keys requires a read-write key with full profile access. - [List API keys](/api-reference/api-keys/list-api-keys): Returns the API keys for the authenticated account. The full key value is never returned; only a masked `keyPreview` is shown. Read-only and read-write API keys are accepted. - [Create an API key](/api-reference/api-keys/create-api-key): Creates an API key and returns the full key value exactly once. This endpoint requires a read-write API key with access to all profiles; profile-scoped keys receive a `403`. Store the returned `key` securely — it cannot be retrieved again after this response. - [Delete an API key](/api-reference/api-keys/delete-api-key): Permanently revokes and deletes an API key. Requires a read-write key with full profile access. A key may delete itself. - Comment Automations: Instagram comment and story reply automations with private replies and delivery logs. - [List comment automations](/api-reference/comment-automations/list-comment-automations): Returns visible automations newest first, maximum 100. Reads use the plan per-minute limit. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - [Create a comment automation](/api-reference/comment-automations/create-comment-automation): Requires read_write. Limited to 60 creates per hour per user. Per-post automations take priority over account-wide automations. One matching automation wins, and each contact receives at most one pending or successful send per automation and source. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - [Get a comment automation](/api-reference/comment-automations/get-comment-automation): Returns the automation and 20 newest logs. Out-of-scope ids return 404 automationNotFound. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - [Update a comment automation](/api-reference/comment-automations/update-comment-automation): Requires read_write. Limited to 120 updates per hour per user. Inactive automations are not evaluated; pending deliveries are skipped when disabled. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - [Delete a comment automation](/api-reference/comment-automations/delete-comment-automation): Requires read_write. Limited to 60 deletes per hour per user. Deletes the automation and its logs in batches. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - [List comment automation logs](/api-reference/comment-automations/list-comment-automation-logs): Newest logs first. total is capped at 5000 matching rows. Misses are not logged. Out-of-scope ids return 404 automationNotFound. Instagram only. A comment permits one private reply ever, within 7 days. Story replies use normal DMs inside the 24-hour messaging window. Buttons and image cards (template) fall back to text with title and URL lines if Meta rejects the template. Reconnect the account on 403 platformCapabilityMissing to grant instagram_business_manage_comments and instagram_business_manage_messages. - Contacts: Profile-owned people from the API, inbox, and comment automations. Maximum 5000 contacts per user and 20 channels per contact. Message counters start when Contacts ships; historical messages are not backfilled. - [List contacts](/api-reference/contacts/list-contacts): Returns contacts in the API key scope, newest first. Requires read_only. Search matches name, email or company. Tag filters use any match. Account and platform filters use contact channels. filters.tags always covers the unfiltered in-scope set. Reads use the plan per-minute budget. - [Create a contact](/api-reference/contacts/create-contact): Requires read_write. Limited to 600 creates/hour/user. Creates a contact and optionally its first channel. Maximum 5000 contacts per user and 20 channels per contact. Unknown accounts return 404 accountNotFound; accounts in another profile or platform mismatches return 400 validation. Out-of-scope profiles return 403 forbidden. - [Bulk create contacts](/api-reference/contacts/bulk-create-contacts): Requires read_write. Limited to 20 imports/hour/user. Runs as one mutation. Processes 1–1000 rows with individual errors; duplicate channel or profile email skips the row and merges only tags. Remaining rows fail once the contact cap is reached. platform is ignored with accountId and rejected without it. Envelope profile/account errors fail the request. - [Get a contact](/api-reference/contacts/get-contact): Requires read_only. Returns the contact with distinct conversationIds and channels, oldest first. Unknown or out-of-scope ids return 404 contactNotFound. Counters start when Contacts ships; history is not backfilled. - [Update a contact](/api-reference/contacts/update-contact): Requires read_write. Limited to 600 updates/hour/user. Supports partial updates, null clears for email, company, avatarUrl, notes and customFields, and [] to clear tags. customFields replaces the entire object. profileId and channel fields cannot be changed. Unknown or out-of-scope ids return 404 contactNotFound. - [Delete a contact](/api-reference/contacts/delete-contact): Requires read_write. Limited to 300 deletes/hour/user. Atomically deletes the contact and all its channels (maximum 20). Unknown or out-of-scope ids return 404 contactNotFound. - [Get contact channels](/api-reference/contacts/get-contact-channels): Requires read_only. Returns all channels oldest first by createdAt, including lastActiveAt. Unknown or out-of-scope contacts return 404 contactNotFound. - Webhooks: Manage outbound webhook endpoints and retained delivery logs. PostZen sends an at-least-once JSON envelope `{ id, type, apiVersion, createdAt, data }` using `POST`; automatic retries and manual redeliveries keep the same event id and raw body. Requests include `X-PostZen-Event`, `X-PostZen-Event-Id`, `X-PostZen-Delivery-Id`, and `X-PostZen-Timestamp`. When a signing secret is configured, `X-PostZen-Signature` is `v1=` where the digest is HMAC-SHA256 over `.`. Production receivers should configure signing, compare signatures in constant time, reject timestamps older than five minutes, and deduplicate by event id. - [List webhooks](/api-reference/webhooks/list-webhooks): Returns webhook endpoints visible to the API key, newest first. Secret and custom header values are never returned. - [Create a webhook](/api-reference/webhooks/create-webhook): Creates an outbound webhook endpoint. A user may have up to 25 endpoints. This endpoint requires a read-write API key. - [Get a webhook](/api-reference/webhooks/get-webhook): Returns one webhook endpoint visible to the API key without secret or custom header values. - [Update a webhook](/api-reference/webhooks/update-webhook): Updates one or more endpoint settings. Omitted fields are unchanged. This endpoint requires a read-write API key. - [Delete a webhook](/api-reference/webhooks/delete-webhook): Deletes an endpoint and stops new event fan-out. Retained delivery logs remain available until their retention period ends. - [Test a webhook](/api-reference/webhooks/test-webhook): Queues a `webhook.test` event through the normal delivery pipeline using the endpoint's current URL, secret, and custom headers. The endpoint must be active. - [List webhook deliveries](/api-reference/webhooks/list-webhook-deliveries): Returns retained delivery logs visible to the API key, newest first. Results are capped to the 1000 most recent matching deliveries. - [Redeliver a webhook event](/api-reference/webhooks/redeliver-webhook-delivery): Queues a new six-attempt delivery run on the retained delivery record while preserving its event id and attempt history. The endpoint must still exist and be active, and the delivery must not already be in progress. - Webhook Events: Payloads PostZen sends to your webhook endpoints, grouped by event family. Every delivery is the JSON envelope `{ id, type, apiVersion, createdAt, data }`; `type` names the event and `data` has the shape documented for its family. Events that describe related changes (a target event and its post event, for example) are delivered independently, so do not rely on their order. - [Post events](/api-reference/webhook-events/post-post-events): - `post.scheduled`: A post became scheduled or its scheduled time changed, including moves by a queue reshuffle; content-only edits, drafts, and posts published immediately do not emit it. - `post.published`: Every target of a post published successfully. - `post.partially_failed`: A post finished publishing with at least one target published and the rest failed or canceled. - `post.failed`: A post finished publishing and none of its targets published. - `post.cancelled`: A scheduled or queued post was deleted (`reason: deleted`) or moved back to a draft (`reason: unscheduled`) before it published. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Post target events](/api-reference/webhook-events/post-post-target-events): - `post.platform.published`: One platform target of a post published; sent once per target. - `post.platform.failed`: One platform target of a post failed with no further automatic retries; a retry that fails again emits it again. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [External post events](/api-reference/webhook-events/post-external-post-events): - `post.external.created`: External post sync found a post published directly on the platform; posts published before the account was connected and posts found by the account's first discovery run (history import) do not emit it. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Account events](/api-reference/webhook-events/post-account-events): - `account.connected`: An account was connected for the first time, or a disconnected or needs-reauth account was reconnected (`reason: reconnected`); re-authorizing an account that is already connected emits nothing. - `account.needs_reauth`: An account's credentials stopped working and the account must be reconnected. - `account.disconnected`: An account was disconnected because the platform revoked access, the user disconnected it in the dashboard or deleted it through the API (`reason: user_disconnected`), or its profile was deleted (`reason: profile_deleted`). Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Analytics events](/api-reference/webhook-events/post-analytics-events): - `analytics.synced`: An account's metrics sync completed; `postsSynced` counts the posts whose metrics were refreshed. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Conversation events](/api-reference/webhook-events/post-conversation-events): - `conversation.started`: A new direct-message conversation appeared on a connected account; conversations imported by the account's first inbox sync do not emit it. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Message events](/api-reference/webhook-events/post-message-events): - `message.received`: A new direct message arrived from another user; only new messages emit it, not history imported when a conversation is first synced. - `message.sent`: A message was sent through PostZen (dashboard, API, or a comment automation, including its Opening DM, follow-request, and nudge messages as well as the final DM); messages sent from the platform's own app do not emit it. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Comment events](/api-reference/webhook-events/post-comment-events): - `comment.received`: A new comment from someone other than the account arrived on one of the account's posts; comments imported when a post is first synced do not emit it. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Contact tag events](/api-reference/webhook-events/post-contact-tag-events): - `contact.tag_added`: A tag was added to a contact, including each tag on a newly created contact; bulk contact imports emit no contact events. - `contact.tag_removed`: A tag was removed from a contact. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Contact field events](/api-reference/webhook-events/post-contact-field-events): - `contact.field_changed`: A custom field on a contact was set, changed, or cleared; bulk contact imports emit no contact events. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`. - [Test event](/api-reference/webhook-events/post-test-events): - `webhook.test`: Sent by `POST /v1/webhooks/{webhookId}/test` to check that the endpoint receives and verifies deliveries. Delivered as a `POST` to every active endpoint that subscribes to the event and whose profile access covers it. Delivery is at least once and events are not ordered, so deduplicate by `id`.