# Postify for developers Automate social publishing across 11 platforms with a versioned REST API, Standard-Webhooks-signed events, and a remote MCP server. - 17 REST operations at `https://app.usepostify.com/v1` (OpenAPI 3.1) - RFC 9457 problem+json errors · draft-11 rate-limit headers · idempotency keys · cursor pagination - Outbound webhooks: 5 event types, Standard Webhooks signatures, durable retries - MCP server for AI agents: `claude mcp add --transport http postify https://app.usepostify.com/api/mcp` (10 tools, OAuth 2.1; publish/delete off until the org owner enables them) - TypeScript SDK `@postify/sdk` — open source, arriving on npm soon Get an API key: https://app.usepostify.com/settings/api-keys (org owner/admin). Docs: https://usepostify.com/docs/api.md. Spec: https://app.usepostify.com/v1/openapi.json. ## Rate limits | Plan | API requests / month | Per-key burst / minute | | --- | --- | --- | | Free | 1,000 | 30 | | Starter | 10,000 | 60 | | Team | 50,000 | 120 | | Agency | 250,000 | 300 | ## For AI agents Paste-prompt for your agent: ``` Help me build against the Postify API. Read https://usepostify.com/llms.txt first — it indexes Markdown twins of every docs page. Base URL: https://app.usepostify.com/v1, auth: Authorization: Bearer postify_live_… (I'll provide the key). Use Idempotency-Key on POST /v1/posts, honor Retry-After on 429, and note that delete/publish-now return 403 dangerous_ops_disabled until the workspace owner opts in. ``` --- # Postify API documentation REST API v1 for Postify, the AI social media scheduler. Base URL: `https://app.usepostify.com/v1`. ## Quickstart 1. Mint an organization API key at [Settings → API keys](https://app.usepostify.com/settings/api-keys) (org owner/admin only). Keys start with `postify_live_` and are shown once. 2. Call the API: `curl https://app.usepostify.com/v1/channels -H "Authorization: Bearer postify_live_…"` 3. Use a channel's `id` to create your first post via [createPost](https://usepostify.com/docs/api/createPost.md). ## Authentication Send the key as `Authorization: Bearer postify_live_…` or in an `x-api-key` header. Keys are organization-scoped and carry the scopes chosen at mint. ## Scopes - `posts:read` — View your posts and drafts - `posts:write` — Create and edit posts - `channels:read` — See your connected social channels - `channels:write` — Manage channel connections - `analytics:read` — View analytics - `webhooks:read` — View webhooks - `webhooks:write` — Manage webhooks - `ai:generate` — Use AI generation (consumes your AI credits) ## Errors RFC 9457 `application/problem+json`: `{ type, title, status, code, detail, request_id }`. Branch on `code`; `type` resolves to a docs page. [All 14 error codes](https://usepostify.com/docs/api/problems.md). ## Pagination List endpoints return `{ data, has_more, next_cursor }`. Pass `limit` (1–100) and `after` (a previous page's `next_cursor`). ## Idempotency POST /v1/posts accepts an `Idempotency-Key` header (24h ledger). Replays return the recorded response with `Idempotency-Replayed: true`; the same key with a different body is a 422 `idempotency_key_reused`. ## Rate limits Responses carry IETF draft-11 `RateLimit`/`RateLimit-Policy` headers plus the `X-RateLimit-*` trio. Two distinct 429s: `rate_limited` (per-key burst — honor `Retry-After`) and `quota_exhausted` (monthly plan allowance — retrying is pointless until reset). | Plan | API requests / month | Per-key burst / minute | | --- | --- | --- | | Free | 1,000 | 30 | | Starter | 10,000 | 60 | | Team | 50,000 | 120 | | Agency | 250,000 | 300 | ## Dangerous operations `DELETE /v1/posts/{id}` and `POST /v1/posts/{id}/publish` return 403 `dangerous_ops_disabled` until an org owner enables "Dangerous AI operations" in [Settings → API keys](https://app.usepostify.com/settings/api-keys). Drafts and scheduled posts need no toggle. ## Operations ### Analytics - [getAnalytics](https://usepostify.com/docs/api/getAnalytics.md) — `GET /v1/analytics` — Get workspace analytics ### Channels - [listChannels](https://usepostify.com/docs/api/listChannels.md) — `GET /v1/channels` — List channels ### Media - [listMedia](https://usepostify.com/docs/api/listMedia.md) — `GET /v1/media` — List media assets - [createMediaUpload](https://usepostify.com/docs/api/createMediaUpload.md) — `POST /v1/media/uploads` — Start a media upload - [completeMediaUpload](https://usepostify.com/docs/api/completeMediaUpload.md) — `POST /v1/media/uploads/{id}/complete` — Complete a media upload ### Posts - [listPosts](https://usepostify.com/docs/api/listPosts.md) — `GET /v1/posts` — List posts - [createPost](https://usepostify.com/docs/api/createPost.md) — `POST /v1/posts` — Create a post - [getPost](https://usepostify.com/docs/api/getPost.md) — `GET /v1/posts/{id}` — Get a post - [reschedulePost](https://usepostify.com/docs/api/reschedulePost.md) — `PATCH /v1/posts/{id}` — Reschedule a post - [deletePost](https://usepostify.com/docs/api/deletePost.md) — `DELETE /v1/posts/{id}` — Delete a post - [publishPost](https://usepostify.com/docs/api/publishPost.md) — `POST /v1/posts/{id}/publish` — Publish a post now ### Usage - [getUsage](https://usepostify.com/docs/api/getUsage.md) — `GET /v1/usage` — Get plan usage ### Webhooks - [listWebhookEndpoints](https://usepostify.com/docs/api/listWebhookEndpoints.md) — `GET /v1/webhook-endpoints` — List webhook endpoints - [createWebhookEndpoint](https://usepostify.com/docs/api/createWebhookEndpoint.md) — `POST /v1/webhook-endpoints` — Create a webhook endpoint - [updateWebhookEndpoint](https://usepostify.com/docs/api/updateWebhookEndpoint.md) — `PATCH /v1/webhook-endpoints/{id}` — Update a webhook endpoint - [deleteWebhookEndpoint](https://usepostify.com/docs/api/deleteWebhookEndpoint.md) — `DELETE /v1/webhook-endpoints/{id}` — Delete a webhook endpoint - [testWebhookEndpoint](https://usepostify.com/docs/api/testWebhookEndpoint.md) — `POST /v1/webhook-endpoints/{id}/test` — Send a test delivery ## More - [Webhooks event catalog & verification](https://usepostify.com/docs/api/webhooks.md) - [Interactive reference (Scalar)](https://app.usepostify.com/docs/api-reference) - [OpenAPI 3.1 spec](https://app.usepostify.com/v1/openapi.json) - [Developer overview](https://usepostify.com/developers.md) - [For AI agents](https://usepostify.com/docs/ai.md) · [llms.txt](https://usepostify.com/llms.txt) - [Changelog](https://usepostify.com/changelog.md) --- # getAnalytics — Postify API `GET /v1/analytics` Get workspace analytics. Workspace-level publishing summary: post counts, delivery success, a 14-day publish timeline, and summed engagement across synced platforms. - Required scope: `analytics:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Responses - `200` — The analytics summary. (schema: `Analytics`) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/analytics \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/analytics", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # listChannels — Postify API `GET /v1/channels` List channels. Lists every social account connected to the organization, with connection health. Use a channel's `id` when creating post variants. Not paginated — the channel count is plan-bounded. - Required scope: `channels:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Responses - `200` — The organization's connected channels. (schema: `ChannelList`) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/channels \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/channels", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # listMedia — Postify API `GET /v1/media` List media assets. Lists the organization's media library, newest first, with cursor pagination. Use an asset's `url` as `media[].url` when creating posts. - Required scope: `posts:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `limit` (query, integer) — Page size (1–100). - `after` (query, string) — Opaque cursor from a previous page's `next_cursor`. ## Responses - `200` — One page of media assets. (schema: `MediaList`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/media \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/media", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # createMediaUpload — Postify API `POST /v1/media/uploads` Start a media upload. Issues a presigned upload ticket: PUT the raw file bytes to `upload_url` with the same `Content-Type`, then call POST /v1/media/uploads/{id}/complete. The asset kind (image/video/audio) is inferred from `content_type`. - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Request body - `filename` (string, required) — Filename including extension. - `content_type` (string, required) — MIME type of the bytes you will upload. - `size_bytes` (integer) — Size in bytes, if known. ## Responses - `201` — The upload ticket. (schema: `CreateUploadResponse`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/media/uploads \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "filename": "launch-teaser.mp4", "content_type": "video/mp4", "size_bytes": 4718592 }' ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/media/uploads", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "filename": "launch-teaser.mp4", "content_type": "video/mp4", "size_bytes": 4718592 }), }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # completeMediaUpload — Postify API `POST /v1/media/uploads/{id}/complete` Complete a media upload. Finalizes an uploaded asset after the PUT succeeds: probes the object, extracts a poster frame for videos, and marks it `ready`. Safe to retry — finalizing an already-ready asset is a no-op. - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Asset identifier. ## Responses - `200` — The finalized asset. (schema: `MediaAsset`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/media/uploads/post_9x2y3z0001/complete \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/media/uploads/post_9x2y3z0001/complete", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # listPosts — Postify API `GET /v1/posts` List posts. Lists the organization's posts, newest first, with cursor pagination. Each post carries its per-channel variants and latest delivery outcomes. - Required scope: `posts:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `limit` (query, integer) — Page size (1–100). - `after` (query, string) — Opaque cursor from a previous page's `next_cursor`. - `status` (query, "draft" | "scheduled" | "publishing" | "published" | "failed" | "needs_approval") — Filter by lifecycle state. ## Responses - `200` — One page of posts. (schema: `PostList`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/posts \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # createPost — Postify API `POST /v1/posts` Create a post. Creates a post with one variant per target channel. Exactly one mode is required: `draft: true` (save only), `scheduled_at` (future publish), or `publish_now: true` (immediate — requires the workspace's dangerous-operations toggle for API callers). Send an `Idempotency-Key` header to make retries safe. - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header - Idempotency: send an `Idempotency-Key` header (1–255 printable ASCII chars, e.g. a UUID). Replays of the same key + body return the recorded response with `Idempotency-Replayed: true`; the same key with a different body is rejected with 422 `idempotency_key_reused`. Ledger window: 24h. ## Request body - `variants` (array of object, required) — One variant per target channel. - `channel_id` (string, required) — A connected channel's `id` (see /v1/channels). - `body` (string, required) — The text to publish to this channel. - `media` (array of PostMediaItem) — Media attachments for this channel. - `title` (string | null) — Internal title (not published). - `body` (string | null) — Optional channel-agnostic base text. - `draft` (boolean) — Save as a draft (no scheduling, no publishing). - `scheduled_at` (string (ISO-8601)) — Future publish time (ISO-8601). Mutually exclusive with `draft` and `publish_now`. - `publish_now` (boolean) — Publish immediately. Requires the workspace's dangerous-operations toggle for API callers. ## Responses - `201` — The created post. (schema: `Post`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md), [`dangerous_ops_disabled`](https://usepostify.com/docs/api/problems/dangerous-ops-disabled.md) - `409` — [`idempotency_in_progress`](https://usepostify.com/docs/api/problems/idempotency-in-progress.md) - `422` — [`idempotency_key_reused`](https://usepostify.com/docs/api/problems/idempotency-key-reused.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/posts \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "variants": [ { "channel_id": "ch_9x2y3z0001", "body": "Shipping day 🚀 Our new analytics dashboard is live — see what actually lands with your audience." } ], "scheduled_at": "2026-08-01T09:00:00Z" }' ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, "Idempotency-Key": crypto.randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ "variants": [ { "channel_id": "ch_9x2y3z0001", "body": "Shipping day 🚀 Our new analytics dashboard is live — see what actually lands with your audience." } ], "scheduled_at": "2026-08-01T09:00:00Z" }), }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # getPost — Postify API `GET /v1/posts/{id}` Get a post. Fetches one post with its variants and latest per-channel delivery outcomes. Poll this after publishing to track delivery. - Required scope: `posts:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Post identifier. ## Responses - `200` — The post. (schema: `Post`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/posts/post_9x2y3z0001 \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts/post_9x2y3z0001", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # reschedulePost — Postify API `PATCH /v1/posts/{id}` Reschedule a post. Moves a draft, scheduled, or failed post to a new future publish time (drafts must already have variants). Published, publishing, and approval-pending posts cannot be rescheduled (409 `resource_conflict`). - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Post identifier. ## Request body - `scheduled_at` (string (ISO-8601), required) — The new future publish time (ISO-8601). ## Responses - `200` — The rescheduled post. (schema: `Post`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X PATCH https://app.usepostify.com/v1/posts/post_9x2y3z0001 \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-08-02T15:30:00Z" }' ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts/post_9x2y3z0001", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "scheduled_at": "2026-08-02T15:30:00Z" }), }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # deletePost — Postify API `DELETE /v1/posts/{id}` Delete a post. Deletes a post (it disappears from Postify; already-published platform content is not retracted). A post that is currently publishing cannot be deleted (409). Requires the workspace's dangerous-operations toggle. - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Post identifier. ## Responses - `200` — Deletion confirmed. (schema: `DeletePostResponse`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md), [`dangerous_ops_disabled`](https://usepostify.com/docs/api/problems/dangerous-ops-disabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X DELETE https://app.usepostify.com/v1/posts/post_9x2y3z0001 \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts/post_9x2y3z0001", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # publishPost — Postify API `POST /v1/posts/{id}/publish` Publish a post now. Queues an immediate publish of a draft, scheduled, or failed post (202 — delivery continues in the background; poll GET /v1/posts/{id} for per-channel outcomes). Already-published posts return 409; a post already publishing is accepted as-is. Requires the workspace's dangerous-operations toggle. - Required scope: `posts:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Post identifier. ## Responses - `202` — Publish accepted. (schema: `PublishPostResponse`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md), [`dangerous_ops_disabled`](https://usepostify.com/docs/api/problems/dangerous-ops-disabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/posts/post_9x2y3z0001/publish \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/posts/post_9x2y3z0001/publish", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # getUsage — Postify API `GET /v1/usage` Get plan usage. The workspace's effective plan tier and every plan meter's consumption for the current period — including `api_requests`, the meter every /v1 call consumes. - Required scope: `analytics:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Responses - `200` — Plan + meter consumption. (schema: `Usage`) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/usage \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/usage", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # listWebhookEndpoints — Postify API `GET /v1/webhook-endpoints` List webhook endpoints. Lists the organization's webhook endpoints with their subscription and failure-ladder state. Signing secrets are never returned here — they are shown once at creation and can be rolled in Settings → Webhooks. - Required scope: `webhooks:read` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Responses - `200` — All webhook endpoints for the organization. (schema: `WebhookEndpointList`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl https://app.usepostify.com/v1/webhook-endpoints \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/webhook-endpoints", { headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # createWebhookEndpoint — Postify API `POST /v1/webhook-endpoints` Create a webhook endpoint. Registers an HTTPS endpoint for event deliveries. The URL is validated against SSRF at creation AND re-validated at every delivery (loopback, private-network, link-local, and cloud-metadata addresses are rejected — 400 `validation_failed`). The response includes the Standard-Webhooks signing secret exactly once. Endpoint creation counts against the plan's `integrations` quota. - Required scope: `webhooks:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Request body - `url` (string, required) — HTTPS URL on the default port (443). Loopback, private, link-local, and cloud-metadata addresses are rejected — at creation AND at every delivery. - `event_types` (array of "post.published" | "post.failed" | "channel.connected" | "channel.reauth_required" | "delivery.failed", required) — Event types to subscribe to (at least one). ## Responses - `201` — The created endpoint with its show-once signing secret. (schema: `WebhookEndpointCreated`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/webhook-endpoints \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.example.com/postify/webhooks", "event_types": [ "post.published", "post.failed" ] }' ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/webhook-endpoints", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://api.example.com/postify/webhooks", "event_types": [ "post.published", "post.failed" ] }), }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # updateWebhookEndpoint — Postify API `PATCH /v1/webhook-endpoints/{id}` Update a webhook endpoint. Updates the URL (re-validated through the SSRF gauntlet), the event-type subscription list, and/or `enabled`. Setting `enabled: true` on an auto-disabled endpoint clears its failure ladder and resumes deliveries. - Required scope: `webhooks:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Webhook endpoint id. ## Request body Partial update — at least one field is required. - `url` (string) — Replacement delivery URL — re-validated through the same SSRF gauntlet as creation. - `event_types` (array of "post.published" | "post.failed" | "channel.connected" | "channel.reauth_required" | "delivery.failed") — Replacement event-type subscription list. - `enabled` (boolean) — Enable or disable deliveries. Setting `true` on an auto-disabled endpoint clears its failure state and resumes deliveries. ## Responses - `200` — The updated endpoint. (schema: `WebhookEndpoint`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X PATCH https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001 \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "enabled": true }), }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # deleteWebhookEndpoint — Postify API `DELETE /v1/webhook-endpoints/{id}` Delete a webhook endpoint. Removes the endpoint and its delivery history. Deliveries stop immediately; this cannot be undone. - Required scope: `webhooks:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Webhook endpoint id. ## Responses - `200` — Deletion confirmed. (schema: `DeleteWebhookEndpointResponse`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X DELETE https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001 \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # testWebhookEndpoint — Postify API `POST /v1/webhook-endpoints/{id}/test` Send a test delivery. Sends a synthetic `webhook.test` event to the endpoint, signed exactly like a real delivery (Standard-Webhooks headers over the raw body). Use it to verify your signature handling before subscribing to real events. Disabled endpoints return 409 `resource_conflict`. - Required scope: `webhooks:write` - Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header ## Parameters - `id` (path, string, required) — Webhook endpoint id. ## Responses - `200` — The test delivery outcome. (schema: `TestWebhookEndpointResult`) - `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) - `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) - `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) - `404` — [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) - `409` — [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) - `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) - `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://app.usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json). ## Example (curl) ```bash curl -X POST https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001/test \ -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" ``` ## Example (TypeScript) ```ts const res = await fetch("https://app.usepostify.com/v1/webhook-endpoints/post_9x2y3z0001/test", { method: "POST", headers: { Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`, }, }); if (!res.ok) { // RFC 9457 problem+json: branch on the stable `code`. const problem = await res.json(); throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`); } const data = await res.json(); ``` --- # Postify webhooks — event catalog & verification Subscribe HTTPS endpoints to workspace events at [Settings → Webhooks](https://app.usepostify.com/settings/webhooks) or via [POST /v1/webhook-endpoints](https://usepostify.com/docs/api/createWebhookEndpoint.md). Deliveries are signed per the [Standard Webhooks](https://www.standardwebhooks.com) spec. ## Envelope Every delivery body is `{ id, type, createdAt, data }`. `id` equals the `webhook-id` header and is identical across retries — use it as your dedup key (delivery is at-least-once). `data` is event-specific. ## Event types ### post.published A publish run finished with at least one variant delivered to a real platform. `succeeded`/`failed` count per-channel variants — a mixed run still emits post.published (check `failed` for partial delivery). ```json { "id": "evt_01j9x2y3z4abcdefghijklmnop", "type": "post.published", "createdAt": "2026-07-20T14:30:00.000Z", "data": { "postId": "cmc9x2y3z0001abcd", "type": "single", "attempts": 2, "succeeded": 2, "failed": 0, "publishedAt": "2026-07-20T14:05:12.000Z" } } ``` ### post.failed A publish run finished with zero successful deliveries — every target channel failed. `publishedAt` is null. ```json { "id": "evt_01j9x2y3z4abcdefghijklmnop", "type": "post.failed", "createdAt": "2026-07-20T14:30:00.000Z", "data": { "postId": "cmc9x2y3z0001abcd", "type": "single", "attempts": 2, "succeeded": 0, "failed": 2, "publishedAt": null } } ``` ### channel.connected A social account finished its OAuth (or app-password) connect flow and is ready to receive posts. ```json { "id": "evt_01j9x2y3z4abcdefghijklmnop", "type": "channel.connected", "createdAt": "2026-07-20T14:30:00.000Z", "data": { "channelId": "cmc9aa1bb0002efgh", "platform": "linkedin", "handle": "Acme Inc" } } ``` ### channel.reauth_required A channel's platform token could not be refreshed and the user must reconnect the account. Posts targeting this channel will fail until it is re-authorized. ```json { "id": "evt_01j9x2y3z4abcdefghijklmnop", "type": "channel.reauth_required", "createdAt": "2026-07-20T14:30:00.000Z", "data": { "channelId": "cmc9aa1bb0002efgh", "platform": "x", "handle": "@acme" } } ``` ### delivery.failed A manual per-channel delivery retry failed again. `error` carries a platform-response excerpt for debugging. ```json { "id": "evt_01j9x2y3z4abcdefghijklmnop", "type": "delivery.failed", "createdAt": "2026-07-20T14:30:00.000Z", "data": { "deliveryAttemptId": "cmc9cc2dd0003ijkl", "postId": "cmc9x2y3z0001abcd", "attemptCount": 3, "error": "429 Too Many Requests from platform API" } } ``` ## Verifying signatures Headers: `webhook-id`, `webhook-timestamp` (unix seconds), `webhook-signature` (`v1,`; multiple space-separated during secret rotation). The signature is HMAC-SHA256 over `${id}.${timestamp}.${rawBody}` with your base64-decoded `whsec_` secret. Verify over the RAW request bytes, enforce ±5 minutes timestamp tolerance, and accept any one matching signature. `@postify/sdk` ships a verifier (open source, arriving on npm soon). ```js import { createHmac, timingSafeEqual } from "node:crypto"; // IMPORTANT: verify over the RAW request bytes — parsing and re-serializing // the JSON changes the bytes and breaks the signature. export function verifyWebhook( rawBody: string, headers: Record, secret: string, // "whsec_…" from endpoint creation (shown once) ): boolean { const id = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; const signatures = headers["webhook-signature"]; // "v1, v1,…" if (!id || !timestamp || !signatures) return false; // Reject stale/future timestamps (±5 minutes) to block replays. const skew = Math.abs(Date.now() / 1000 - Number(timestamp)); if (!Number.isFinite(skew) || skew > 300) return false; const key = Buffer.from(secret.slice("whsec_".length), "base64"); const expected = createHmac("sha256", key) .update(`${id}.${timestamp}.${rawBody}`) .digest(); // Multiple space-separated signatures appear during secret rotation — // accept if ANY matches. return signatures.split(" ").some((candidate) => { const [version, sig] = candidate.split(","); if (version !== "v1" || !sig) return false; const given = Buffer.from(sig, "base64"); return given.length === expected.length && timingSafeEqual(given, expected); }); } ``` ## Retries & failure ladder Failed deliveries retry on this schedule after the initial attempt: 30s, 2m, 10m, 30m, 2h, 5h, 10h, 14h — 9 attempts over ~31.7 hours. Warning emails go out at 5/10/15 consecutive failures; the endpoint auto-disables at 20. Any 2xx resets the counter; re-enable an auto-disabled endpoint via PATCH `enabled: true` or in settings. Delivery is at-least-once: dedup on `webhook-id`. Endpoint URLs must be public HTTPS on port 443 — loopback, private, link-local, and cloud-metadata addresses are rejected at creation and at every delivery. --- # Postify API error codes Every /v1 error is RFC 9457 `application/problem+json`; the `type` URI resolves to one of these pages and `code` is the stable machine-readable discriminant. - [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md) — HTTP 400, Invalid request - [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md) — HTTP 400, Request validation failed - [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md) — HTTP 401, Authentication required - [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md) — HTTP 401, Invalid API key - [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md) — HTTP 403, API credential lacks the required scope - [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md) — HTTP 403, Feature not available on the current plan - [`dangerous_ops_disabled`](https://usepostify.com/docs/api/problems/dangerous-ops-disabled.md) — HTTP 403, Dangerous AI operations are disabled for this workspace - [`resource_not_found`](https://usepostify.com/docs/api/problems/resource-not-found.md) — HTTP 404, Resource not found - [`resource_conflict`](https://usepostify.com/docs/api/problems/resource-conflict.md) — HTTP 409, Resource state conflicts with the request - [`idempotency_in_progress`](https://usepostify.com/docs/api/problems/idempotency-in-progress.md) — HTTP 409, A request with this idempotency key is still in progress - [`idempotency_key_reused`](https://usepostify.com/docs/api/problems/idempotency-key-reused.md) — HTTP 422, Idempotency key reused with a different request body - [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md) — HTTP 429, Rate limit exceeded - [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md) — HTTP 429, Plan quota exhausted for this billing period - [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md) — HTTP 500, Internal server error --- # invalid_request — Postify API error HTTP 400 · Invalid request This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/invalid-request`. The request was structurally wrong before validation even ran — malformed JSON, a missing request body where one is required, an unusable query parameter, or a malformed Idempotency-Key header. ## Common causes - The request body is not valid JSON (trailing comma, unquoted key, truncated payload). - The Content-Type header is missing or not application/json on a body-carrying request. - A query parameter has an unusable value — e.g. limit outside 1–100, or an unparseable cursor. ## How to fix - Serialize the body with a JSON library rather than string concatenation. - Send Content-Type: application/json on every POST/PATCH. - Read the `detail` field — it names what was malformed. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/invalid-request", "title": "Invalid request", "status": 400, "code": "invalid_request", "detail": "Request body is not valid JSON.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [API quickstart](https://usepostify.com/docs/api) - [validation_failed (schema-level errors)](https://usepostify.com/docs/api/problems/validation-failed) --- # validation_failed — Postify API error HTTP 400 · Request validation failed This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/validation-failed`. The request parsed, but one or more fields failed schema validation. The `errors` array lists every failing field with a JSON-pointer-style path and a message — fix them all in one pass. ## Common causes - A required field is missing (e.g. variants on POST /v1/posts). - A field is the wrong type or out of range (body over the length cap, scheduled_at not ISO-8601, an unknown webhook event type). - Mutually exclusive fields sent together (e.g. draft plus scheduled_at). ## How to fix - Iterate the `errors` array — each entry carries the field path and what's wrong with it. - Check the operation's request-body table in these docs for required fields, types, and limits. - Send timestamps as full ISO-8601 UTC strings, e.g. 2026-08-01T09:00:00Z. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/validation-failed", "title": "Request validation failed", "status": 400, "code": "validation_failed", "detail": "1 field failed validation.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Operation reference](https://usepostify.com/docs/api) - [Interactive schema reference (Scalar)](https://app.usepostify.com/docs/api-reference) --- # authentication_required — Postify API error HTTP 401 · Authentication required This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/authentication-required`. The request reached a /v1 endpoint without any usable credential — no Authorization header, no x-api-key header, or a token that isn't shaped like a Postify API key. ## Common causes - The Authorization header was omitted, or lost by a proxy that strips it. - The header is malformed — e.g. missing the Bearer prefix. - You sent a session cookie; /v1 accepts API keys only. ## How to fix - Send `Authorization: Bearer postify_live_…` or `x-api-key: postify_live_…` on every request. - Mint a key in Settings → API keys (org owners and admins only). - If a proxy sits in front of your client, confirm it forwards the Authorization header. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/authentication-required", "title": "Authentication required", "status": 401, "code": "authentication_required", "detail": "No API key was provided.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Authentication docs](https://usepostify.com/docs/api#authentication) - [Mint an API key](https://app.usepostify.com/settings/api-keys) --- # invalid_api_key — Postify API error HTTP 401 · Invalid API key This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/invalid-api-key`. A credential was presented but it is not a live key — it was revoked, rolled, expired, or never existed. ## Common causes - The key was rolled or revoked in Settings → API keys and the old secret is still deployed. - The key was truncated or whitespace-mangled when copied into your secret store. - You're using a key from a different workspace or environment. ## How to fix - Mint or roll a key in Settings → API keys and update your secret store — keys are shown once at mint. - Compare the key's prefix and last characters against the key list in settings. - After a roll with overlap, finish the migration before the overlap window ends. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/invalid-api-key", "title": "Invalid API key", "status": 401, "code": "invalid_api_key", "detail": "The provided API key is not valid.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Manage API keys](https://app.usepostify.com/settings/api-keys) - [Authentication docs](https://usepostify.com/docs/api#authentication) --- # insufficient_scope — Postify API error HTTP 403 · API credential lacks the required scope This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/insufficient-scope`. The key is valid but was minted without the scope this operation requires. Scopes are fixed at mint — a key can never escalate itself. ## Common causes - The key was minted with a read-only preset and the request writes (e.g. posts:write for POST /v1/posts). - Webhook management needs webhooks:read / webhooks:write, which the key lacks. ## How to fix - Check the required scopes on the operation's docs page — every page lists them. - Mint a new key with the needed scopes in Settings → API keys, then retire the old one. - Prefer many narrowly-scoped keys over one all-scope key. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/insufficient-scope", "title": "API credential lacks the required scope", "status": 403, "code": "insufficient_scope", "detail": "This operation requires the posts:write scope.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Scope list](https://usepostify.com/docs/api#scopes) - [Mint a key with different scopes](https://app.usepostify.com/settings/api-keys) --- # feature_not_enabled — Postify API error HTTP 403 · Feature not available on the current plan This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/feature-not-enabled`. Your plan does not include this feature at all — distinct from quota_exhausted, where the plan includes it but this period's allowance is used up. Retrying will not help; upgrading will. ## Common causes - The workspace's plan doesn't include API access or the specific metered feature. - A downgrade removed a feature the integration relied on. ## How to fix - Upgrade the workspace's plan in Settings → Billing. - Compare plan API allowances on the rate limits table. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/feature-not-enabled", "title": "Feature not available on the current plan", "status": 403, "code": "feature_not_enabled", "detail": "The current plan does not include API access.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Rate limits & plan quotas](https://usepostify.com/docs/api#rate-limits) - [Pricing](https://usepostify.com/pricing) - [quota_exhausted (allowance used up)](https://usepostify.com/docs/api/problems/quota-exhausted) --- # dangerous_ops_disabled — Postify API error HTTP 403 · Dangerous AI operations are disabled for this workspace This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/dangerous-ops-disabled`. DELETE /v1/posts/{id} and POST /v1/posts/{id}/publish are guarded: they can delete content or publish publicly to real social accounts, so they stay off for API callers until a workspace owner explicitly opts in. This is the same toggle that gates MCP agents' publish/delete tools. ## Common causes - The workspace owner hasn't enabled "Dangerous AI operations" in Settings → API keys. - The toggle was switched back off after previously being enabled. ## How to fix - Have an org owner open Settings → API keys and enable Dangerous AI operations. - Until then, create posts as drafts or scheduled posts (allowed) and publish from the app. - Design agent flows to propose-and-confirm: draft by API, let a human publish. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/dangerous-ops-disabled", "title": "Dangerous AI operations are disabled for this workspace", "status": 403, "code": "dangerous_ops_disabled", "detail": "Dangerous operations are disabled for this workspace. An owner can enable them in Settings → API keys.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Enable in Settings → API keys](https://app.usepostify.com/settings/api-keys) - [createPost (drafts don't need the toggle)](https://usepostify.com/docs/api/createPost) --- # resource_not_found — Postify API error HTTP 404 · Resource not found This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/resource-not-found`. No resource with that id is visible to this key's organization. Ids are org-scoped: another workspace's post id answers 404, not 403 — existence is never leaked across organizations. ## Common causes - The id is from a different workspace than the key's. - The resource was deleted. - The id was mangled in transit (truncation, URL-encoding issues). ## How to fix - List the collection (e.g. GET /v1/posts) and confirm the id exists for this workspace. - Check which workspace the key belongs to in Settings → API keys. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/resource-not-found", "title": "Resource not found", "status": 404, "code": "resource_not_found", "detail": "No post with this id exists in this workspace.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [listPosts](https://usepostify.com/docs/api/listPosts) - [listChannels](https://usepostify.com/docs/api/listChannels) --- # resource_conflict — Postify API error HTTP 409 · Resource state conflicts with the request This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/resource-conflict`. The resource exists but its current state rejects this transition — for example publishing a post that is already publishing, rescheduling a published post, or completing a media upload twice. ## Common causes - A publish was requested on a post already claimed for publishing. - A reschedule targeted a post that already published. - An upload was completed twice. ## How to fix - GET the resource first and branch on its `status`. - Treat 409 as a state signal, not an error to retry — re-read, then decide. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/resource-conflict", "title": "Resource state conflicts with the request", "status": 409, "code": "resource_conflict", "detail": "This post has already been published.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [getPost](https://usepostify.com/docs/api/getPost) - [publishPost](https://usepostify.com/docs/api/publishPost) --- # idempotency_in_progress — Postify API error HTTP 409 · A request with this idempotency key is still in progress This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/idempotency-in-progress`. A request with this Idempotency-Key is still executing. The ledger refuses to run the same keyed request concurrently — the first request wins; this one should wait and retry. ## Common causes - A client-side timeout fired and the retry raced the original, still-running request. - Two workers picked up the same job and sent the same keyed request simultaneously. ## How to fix - Wait briefly (about a second) and retry with the SAME key — once the original finishes you'll get its recorded response with Idempotency-Replayed: true. - Don't switch keys to "unstick" it: a new key risks a duplicate post. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/idempotency-in-progress", "title": "A request with this idempotency key is still in progress", "status": 409, "code": "idempotency_in_progress", "detail": "A request with this Idempotency-Key is currently in progress.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Idempotency docs](https://usepostify.com/docs/api#idempotency) - [idempotency_key_reused](https://usepostify.com/docs/api/problems/idempotency-key-reused) --- # idempotency_key_reused — Postify API error HTTP 422 · Idempotency key reused with a different request body This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/idempotency-key-reused`. This Idempotency-Key was already used within its 24-hour window with a DIFFERENT request body. Keys uniquely identify one logical request; reusing one for different content is always a client bug, so the API refuses rather than guessing. ## Common causes - A static or hardcoded key is reused across different posts. - The body is re-serialized differently between attempts (field order is fine; changed values are not). - A key-generation collision (e.g. truncated hash). ## How to fix - Generate one fresh key (a UUID) per logical create, and reuse it only for byte-equivalent retries of that same create. - Persist the key alongside your outbound job so retries reuse it deliberately. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/idempotency-key-reused", "title": "Idempotency key reused with a different request body", "status": 422, "code": "idempotency_key_reused", "detail": "This Idempotency-Key was already used with a different request body.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Idempotency docs](https://usepostify.com/docs/api#idempotency) - [createPost](https://usepostify.com/docs/api/createPost) --- # rate_limited — Postify API error HTTP 429 · Rate limit exceeded This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/rate-limited`. The per-key burst window (requests per minute) is exhausted. This is short-term throttling — unlike quota_exhausted, it clears within seconds. The Retry-After header says exactly how long to wait. ## Common causes - A burst of requests exceeded the key's per-minute limit (30–300/min depending on plan). - Parallel workers share one key and collectively exceed its window. - A retry loop without backoff amplifies itself. ## How to fix - Honor Retry-After, then retry — the official SDK does this automatically for safe requests. - Watch the RateLimit / X-RateLimit-Remaining headers and pace proactively. - Higher plans get bigger burst windows (see the rate limits table). ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/rate-limited", "title": "Rate limit exceeded", "status": 429, "code": "rate_limited", "detail": "Burst limit reached. Retry after 21 seconds.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Rate limits & headers](https://usepostify.com/docs/api#rate-limits) - [quota_exhausted (monthly, not burst)](https://usepostify.com/docs/api/problems/quota-exhausted) --- # quota_exhausted — Postify API error HTTP 429 · Plan quota exhausted for this billing period This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/quota-exhausted`. The workspace's monthly allowance for a metered feature — like api_requests — is used up for this billing period. Retrying is pointless until the period resets or the plan is upgraded; that's why it's a distinct code from rate_limited. ## Common causes - The monthly API request allowance (1,000–250,000 depending on plan) is consumed. - A runaway job burned the allowance early in the period. ## How to fix - Check GET /v1/usage for per-meter used/limit/reset — it's cheap and won't itself be blocked by post quotas. - Upgrade the plan for a bigger monthly allowance. - Back off until the period resets — do NOT retry-loop on this code. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/quota-exhausted", "title": "Plan quota exhausted for this billing period", "status": 429, "code": "quota_exhausted", "detail": "The api_requests allowance for this billing period is exhausted.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [getUsage](https://usepostify.com/docs/api/getUsage) - [Rate limits & plan quotas](https://usepostify.com/docs/api#rate-limits) - [Pricing](https://usepostify.com/pricing) --- # internal_error — Postify API error HTTP 500 · Internal server error This page is the resolution of the problem `type` URI `https://usepostify.com/docs/api/problems/internal-error`. Something failed on Postify's side. The request may or may not have taken effect — which is exactly what idempotency keys are for. Every response carries a request_id; quote it when contacting support. ## Common causes - A transient infrastructure fault. - An unhandled edge case in the API. ## How to fix - Retry with backoff; for POST /v1/posts, retry with the SAME Idempotency-Key so a request that did land isn't duplicated. - If it persists, email support@usepostify.com with the request_id from the response. ## Example response ```json { "type": "https://usepostify.com/docs/api/problems/internal-error", "title": "Internal server error", "status": 500, "code": "internal_error", "detail": "An unexpected error occurred.", "request_id": "req_6f1f8a2b40d34cd2a6f1e2c9b8d7a601" } ``` ## Related - [Idempotency docs](https://usepostify.com/docs/api#idempotency) --- # Postify for AI agents Machine-readable surfaces for building against Postify: - [llms.txt](https://usepostify.com/llms.txt) — curated index of everything below - [llms-full.txt](https://usepostify.com/llms-full.txt) — the full docs as one Markdown document - Markdown twins: append `.md` to any docs URL (e.g. `https://usepostify.com/docs/api/createPost.md`) - [OpenAPI 3.1 spec](https://app.usepostify.com/v1/openapi.json) — all 17 operations with full schemas - MCP server: `claude mcp add --transport http postify https://app.usepostify.com/api/mcp` — 10 tools over OAuth 2.1 ## Guidelines for agents - Auth: `Authorization: Bearer postify_live_…` (keys minted at https://app.usepostify.com/settings/api-keys). - Honor `Retry-After` on 429 `rate_limited`; do NOT retry 429 `quota_exhausted` (monthly allowance — wait for reset or upgrade). - Always send `Idempotency-Key` (a UUID) on POST /v1/posts so retries never double-post. - `DELETE /v1/posts/{id}` and `POST /v1/posts/{id}/publish` need the workspace owner to enable "Dangerous AI operations" first — expect 403 `dangerous_ops_disabled` otherwise. Prefer drafting and letting a human publish. - Dedup webhook deliveries on the `webhook-id` header (at-least-once delivery). Agent paste-prompt: ``` Help me build against the Postify API. Read https://usepostify.com/llms.txt first — it indexes Markdown twins of every docs page. Base URL: https://app.usepostify.com/v1, auth: Authorization: Bearer postify_live_… (I'll provide the key). Use Idempotency-Key on POST /v1/posts, honor Retry-After on 429, and note that delete/publish-now return 403 dangerous_ops_disabled until the workspace owner opts in. ``` --- # Postify changelog Product and API updates, newest first. RSS: https://usepostify.com/changelog/rss.xml - 2026-07-20 — [TypeScript SDK preview + developer docs portal](https://usepostify.com/changelog/typescript-sdk-preview-developer-docs.md): A hand-written TypeScript SDK now wraps the full 17-operation /v1 surface — open source and arriving on npm soon — and this developer docs portal launches with per-operation pages, error-code references, and an AI-readable llms.txt. - 2026-07-20 — [Outbound webhooks: Standard Webhooks signing, durable retries, failure ladder](https://usepostify.com/changelog/outbound-webhooks-standard-webhooks.md): Subscribe HTTPS endpoints to five event types. Every delivery is Standard-Webhooks-signed, retried on a durable 9-attempt schedule, and protected by a failure ladder that warns before auto-disabling a dead endpoint. - 2026-07-19 — [Posts, media, analytics and usage endpoints + idempotency and plan metering](https://usepostify.com/changelog/posts-media-analytics-usage-endpoints.md): The /v1 surface grows to 17 operations: full post lifecycle, media uploads via presigned PUT, workspace analytics, and a usage meter — with a Postgres-backed idempotency ledger and per-plan API metering. - 2026-07-19 — [Public API v1: /v1 launch with OpenAPI 3.1 + interactive reference](https://usepostify.com/changelog/public-api-v1-launch.md): Postify's public REST API ships: organization API keys, RFC 9457 errors, IETF draft-11 rate-limit headers, an OpenAPI 3.1 spec generated from the same schemas the handlers validate with, and an interactive Scalar reference. --- # TypeScript SDK preview + developer docs portal 2026-07-20 — Postify changelog A hand-written TypeScript SDK now wraps the full 17-operation /v1 surface — open source and arriving on npm soon — and this developer docs portal launches with per-operation pages, error-code references, and an AI-readable llms.txt. ## Added - @postify/sdk: a thin, typed TypeScript client covering all 17 /v1 operations — typed RFC 9457 errors, automatic Retry-After retries (never on unkeyed mutations), auto idempotency keys on POST /v1/posts, cursor auto-pagination, and a Standard Webhooks verifier. Open source, arriving on npm soon. - Developer docs on usepostify.com: a static page per operation, a page per error code (the exact URLs API problem `type` fields resolve to), the webhook event catalog, and this changelog. - llms.txt + llms-full.txt, plus a Markdown twin of every docs page at {url}.md — built for AI agents and answer engines. ## Changed - The /developers page is the new front door for the API, SDK, webhooks, and MCP server. --- # Outbound webhooks: Standard Webhooks signing, durable retries, failure ladder 2026-07-20 — Postify changelog Subscribe HTTPS endpoints to five event types. Every delivery is Standard-Webhooks-signed, retried on a durable 9-attempt schedule, and protected by a failure ladder that warns before auto-disabling a dead endpoint. ## Added - /v1/webhook-endpoints: list, create, update, delete, and test-deliver webhook endpoints by API. - Five event types: post.published, post.failed, channel.connected, channel.reauth_required, delivery.failed. - Standard Webhooks signatures (webhook-id / webhook-timestamp / webhook-signature headers, `v1,` HMAC-SHA256 scheme) with show-once signing secrets and dual-signature rotation. - Durable retries: 9 attempts over ~31.7 hours (30s → 14h backoff); warning emails at 5/10/15 consecutive failures; auto-disable at 20. - SSRF gauntlet: delivery URLs are validated against private, loopback, link-local, and cloud-metadata address space — at creation and at every delivery. - Webhook endpoint management UI in Settings → Webhooks. --- # Posts, media, analytics and usage endpoints + idempotency and plan metering 2026-07-19 — Postify changelog The /v1 surface grows to 17 operations: full post lifecycle, media uploads via presigned PUT, workspace analytics, and a usage meter — with a Postgres-backed idempotency ledger and per-plan API metering. ## Added - Posts: list, create (draft / scheduled / publish-now), get, reschedule, delete, and publish — 12 new operations across posts, media, analytics, and usage. - Idempotency-Key on POST /v1/posts: a 24-hour ledger replays the original response verbatim (Idempotency-Replayed: true) and answers key reuse with a different body with 422 idempotency_key_reused. - Media uploads: POST /v1/media/uploads returns a presigned PUT URL; complete the upload to get a library asset usable in posts. - api_requests metering per plan, with distinct errors: 403 feature_not_enabled (plan lacks the API) vs 429 quota_exhausted (monthly allowance consumed). - Per-key burst limits derived from your plan, stamped at key mint and re-synced on billing events. ## Changed - Dangerous operations (DELETE /v1/posts/{id}, POST /v1/posts/{id}/publish) return 403 dangerous_ops_disabled until an org owner enables them in Settings → API keys. --- # Public API v1: /v1 launch with OpenAPI 3.1 + interactive reference 2026-07-19 — Postify changelog Postify's public REST API ships: organization API keys, RFC 9457 errors, IETF draft-11 rate-limit headers, an OpenAPI 3.1 spec generated from the same schemas the handlers validate with, and an interactive Scalar reference. ## Added - GET /v1/channels — list your connected social accounts, the publish targets for every post. - Organization API keys (postify_live_…) minted in Settings → API keys, sent as Authorization: Bearer or x-api-key. - RFC 9457 application/problem+json errors with a stable machine-readable `code` and a `request_id` on every response. - IETF draft-11 RateLimit / RateLimit-Policy headers plus the X-RateLimit-* compatibility trio. - OpenAPI 3.1 spec at /v1/openapi.json and an interactive reference at app.usepostify.com/docs/api-reference.