# SkedCast — Developer API for AI agents > SkedCast is an agency-grade, bulk-first social media scheduling platform that lets teams compose a post once and publish it across up to 500 connected accounts on 10 social networks, pacing each account, verifying that posts went live, and alerting you when a platform refuses one. This page describes the entire SkedCast programmatic surface so an AI agent can integrate in one read. Human docs: https://skedcast.com/developers. Machine contract: https://skedcast.com/openapi.json (OpenAPI 3.1). ## Base URLs - REST API: https://api.skedcast.com/v1 - MCP server: https://api.skedcast.com/mcp - OpenAPI spec: https://skedcast.com/openapi.json ## Authentication All requests send a single secret in the Authorization header: - API key: `Authorization: Bearer sked_live_…` (create in the SkedCast console → Settings → Developer). - OAuth 2.1: `Authorization: Bearer ` (Authorization Code + PKCE; clients register dynamically via RFC 7591). Access tokens are audience-bound (RFC 8707): to call the REST API request `resource=https://api.skedcast.com/v1` at /oauth/authorize (the audience is fixed there; repeat it at /oauth/token, where it is validated when present — `invalid_target` on a mismatch). For the MCP server request `https://api.skedcast.com/mcp` (the default when `resource` is omitted at authorize). A token minted for one resource is rejected by the other. A token's effective permission is always `scopes ∩ role` — it can never exceed what the owning member can do. ## Scopes - agency.read — Read workspace profile - clients.read — Read clients - accounts.read — Read connected social accounts - posts.read — Read posts and schedule - analytics.read — Read analytics - reports.read — Read reports - besttimes.read — Read best-time recommendations - media.read — Read content library - webhooks.read — Read webhook endpoints and delivery logs - approvals.read — Read the approval queue - campaigns.read — Read campaigns - posts.compose — Create and schedule posts - approvals.decide — Approve or reject pending posts - media.manage — Upload and manage library assets - clients.manage — Create and manage clients - accounts.connect — Generate account connect links - webhooks.manage — Create and manage webhook endpoints - webhooks.delete — Delete webhook endpoints - campaigns.manage — Create and manage campaigns ## Conventions - Single resource → `{ data, meta }`; list → `{ data, page, meta }`. `meta.correlationId` is the trace id. - Lists use keyset pagination: pass `limit` (1–100) and the previous `nextCursor` as `cursor`; stop when `hasMore` is false. - Every mutating request — POST, PATCH and DELETE — requires an `Idempotency-Key` header (replays return the original result). - Errors are RFC 9457 `application/problem+json` with a stable `code` field — branch on `code`, not on text. - Media: POST /media returns `readyEvent: "media.ready"` — subscribe to it rather than polling. If you must poll GET /media/{id}, wait on `ready: true` (identical to `publishable: true`). NEVER compare the `status` string against "ready": no such status exists — the terminal value is `transcoded` — and a poll loop that waits for it hangs until its own retry cap on an asset that was ready in seconds. Break on failure too: `failed` and `quarantined` leave `ready` false forever, so exit when `statusCategory` is non-null. Do not wait on a platform appearing in `variants[]`; `readiness` gives one verdict per platform (ready / preparing / failed). - Published posts: a target carries `externalId` (the platform's own post id) and `permalink` (its public URL), on both GET /posts/{id} and GET /post-targets/{id}. `permalink` is the URL the platform itself reported at publish time when we captured one — which is why it works for TikTok, Instagram and Threads, whose URLs cannot be rebuilt from `externalId`. Otherwise it is derived from `externalId` (YouTube, X, Pinterest, LinkedIn, Facebook feed posts). It is null while unpublished, for posts published before 2026-09-03 on the non-derivable platforms, and where the platform gives no URL at all (a SELF_ONLY TikTok post, a Telegram plain group). Null means we do not know the URL, never that there is none: no guessed links are emitted. ## REST operations - GET https://api.skedcast.com/v1/posts (scope: posts.read) — List posts - POST https://api.skedcast.com/v1/posts (scope: posts.compose) — Create a post - POST https://api.skedcast.com/v1/posts/preview (scope: posts.compose) — Validate a post without creating it - POST https://api.skedcast.com/v1/posts/{id}/revise (scope: posts.compose) — Revise a scheduled post - GET https://api.skedcast.com/v1/posts/{id} (scope: posts.read) — Get a post - PATCH https://api.skedcast.com/v1/posts/{id} (scope: posts.compose) — Update a draft post - GET https://api.skedcast.com/v1/posts/{id}/recycle (scope: posts.read) — Get a post's recycle settings - PUT https://api.skedcast.com/v1/posts/{id}/recycle (scope: posts.compose) — Set a post's recycle settings - DELETE https://api.skedcast.com/v1/posts/{id}/recycle (scope: posts.compose) — Clear a post's recycle settings - GET https://api.skedcast.com/v1/post-targets (scope: posts.read) — List post targets - GET https://api.skedcast.com/v1/post-targets/{id} (scope: posts.read) — Get a post target - POST https://api.skedcast.com/v1/post-targets/{id}/reschedule (scope: posts.compose) — Reschedule a post target - POST https://api.skedcast.com/v1/post-targets/{id}/retry (scope: posts.compose) — Retry a post target - POST https://api.skedcast.com/v1/post-targets/{id}/cancel (scope: posts.compose) — Cancel a post target - POST https://api.skedcast.com/v1/post-targets/bulk (scope: posts.compose) — Bulk post-target action - GET https://api.skedcast.com/v1/clients (scope: clients.read) — List clients - POST https://api.skedcast.com/v1/clients (scope: clients.manage) — Create a client - GET https://api.skedcast.com/v1/clients/{id} (scope: clients.read) — Get a client - PATCH https://api.skedcast.com/v1/clients/{id} (scope: clients.manage) — Update a client - DELETE https://api.skedcast.com/v1/clients/{id} (scope: clients.manage) — Archive a client - POST https://api.skedcast.com/v1/clients/{id}/connect-invites (scope: accounts.connect) — Mint an account connect link - GET https://api.skedcast.com/v1/accounts (scope: accounts.read) — List connected accounts - GET https://api.skedcast.com/v1/accounts/cadence (scope: accounts.read) — Posting-limits overview - GET https://api.skedcast.com/v1/quota (scope: accounts.read) — Read quota pre-flight - GET https://api.skedcast.com/v1/accounts/{id} (scope: accounts.read) — Get a connected account - GET https://api.skedcast.com/v1/accounts/{id}/cadence (scope: accounts.read) — Get an account's posting limits - GET https://api.skedcast.com/v1/approvals (scope: approvals.read) — List approvals - GET https://api.skedcast.com/v1/approvals/{id} (scope: approvals.read) — Get an approval - POST https://api.skedcast.com/v1/approvals/{id}/approve (scope: approvals.decide) — Approve a pending request - POST https://api.skedcast.com/v1/approvals/{id}/reject (scope: approvals.decide) — Reject a pending request - POST https://api.skedcast.com/v1/approvals/{id}/request-changes (scope: approvals.decide) — Request changes on a pending request - GET https://api.skedcast.com/v1/campaigns (scope: campaigns.read) — List campaigns - POST https://api.skedcast.com/v1/campaigns (scope: campaigns.manage) — Create a campaign - GET https://api.skedcast.com/v1/campaigns/{id} (scope: campaigns.read) — Get a campaign - PATCH https://api.skedcast.com/v1/campaigns/{id} (scope: campaigns.manage) — Update a campaign - DELETE https://api.skedcast.com/v1/campaigns/{id} (scope: campaigns.manage) — Delete a campaign - POST https://api.skedcast.com/v1/campaigns/{id}/assign (scope: campaigns.manage) — Assign posts to a campaign - POST https://api.skedcast.com/v1/campaigns/{id}/unassign (scope: campaigns.manage) — Unassign posts from a campaign - POST https://api.skedcast.com/v1/campaigns/{id}/shift (scope: campaigns.manage) — Shift a campaign's schedule - GET https://api.skedcast.com/v1/agency (scope: agency.read) — Get the current workspace - GET https://api.skedcast.com/v1/analytics/overview (scope: analytics.read) — Analytics overview - GET https://api.skedcast.com/v1/analytics/posts (scope: analytics.read) — Per-post analytics - GET https://api.skedcast.com/v1/analytics/posts/{id} (scope: analytics.read) — Per-post breakdown - GET https://api.skedcast.com/v1/analytics/accounts/{id} (scope: analytics.read) — Per-account analytics - GET https://api.skedcast.com/v1/analytics/clients/{id} (scope: analytics.read) — Per-client analytics - GET https://api.skedcast.com/v1/reports (scope: reports.read) — List reports - GET https://api.skedcast.com/v1/reports/{id} (scope: reports.read) — Get a report - GET https://api.skedcast.com/v1/reports/{id}/exports/{runId} (scope: reports.read) — Download a report run - GET https://api.skedcast.com/v1/best-times (scope: besttimes.read) — Best-time recommendations - GET https://api.skedcast.com/v1/media (scope: media.read) — List library assets - POST https://api.skedcast.com/v1/media (scope: media.manage) — Register an uploaded asset - GET https://api.skedcast.com/v1/media/{id} (scope: media.read) — Get a library asset - POST https://api.skedcast.com/v1/media/presign (scope: media.manage) — Presign a direct upload - POST https://api.skedcast.com/v1/media/import-url (scope: media.manage) — Import an asset from a URL - GET https://api.skedcast.com/v1/webhooks (scope: webhooks.read) — List webhook endpoints - POST https://api.skedcast.com/v1/webhooks (scope: webhooks.manage) — Create a webhook endpoint - GET https://api.skedcast.com/v1/webhooks/{id} (scope: webhooks.read) — Get a webhook endpoint - PATCH https://api.skedcast.com/v1/webhooks/{id} (scope: webhooks.manage) — Update a webhook endpoint - DELETE https://api.skedcast.com/v1/webhooks/{id} (scope: webhooks.delete) — Delete a webhook endpoint - POST https://api.skedcast.com/v1/webhooks/{id}/test (scope: webhooks.manage) — Send a test delivery - POST https://api.skedcast.com/v1/webhooks/{id}/rotate-secret (scope: webhooks.manage) — Rotate the signing secret - GET https://api.skedcast.com/v1/webhooks/{id}/deliveries (scope: webhooks.read) — List webhook deliveries ## MCP server (connect an AI agent) Endpoint: POST https://api.skedcast.com/mcp (Streamable HTTP, OAuth 2.1 bearer). Discover auth from https://api.skedcast.com/.well-known/oauth-protected-resource. Tools: - list_scheduled_posts (scope: posts.read) — List the workspace's posts (queue, drafts, published) newest-first, with optional status / client / platform filters and free-text search (`q`) over captions, per-platform overrides, client names and account handles. - list_post_targets (scope: posts.read) — List individual post DESTINATIONS (one row per platform account) with optional status / client / account / platform / free-text filters and a SCHEDULE-time window — the tool for "what publishes on this date". Carries `total` and `totalPosts`, so a caller can tell a full page from a complete set. - get_post (scope: posts.read) — Get one post with its per-platform variants and fan-out targets. - list_clients (scope: clients.read) — List the workspace's client groups (brands / influencers). - list_accounts (scope: accounts.read) — List connected social accounts, optionally filtered by client or platform. - get_analytics_overview (scope: analytics.read) — Reach / impressions / engagement plus a per-platform rollup over a date window. - get_post_analytics (scope: analytics.read) — Per-post performance (complements get_analytics_overview): one post’s lifetime per-target breakdown by postId, or a keyset page of per-post rows with filters. - get_account_analytics (scope: analytics.read) — One channel’s totals and daily series, plus the two things no other tool carries: profile-level activity (profile views, link taps, video plays) and follower demographics. - list_library_assets (scope: media.read) — List content-library (DAM) assets with optional kind / folder / label / client / status / search filters. Filter `status: "transcoded"` for assets that are actually publishable; every row also carries `status`, `publishable` and `ready`. - get_capabilities (scope: posts.read) — The per-platform composing rulebook: caption and hashtag limits, media rules per kind (counts, formats, file-size ceiling, duration bounds, aspect ratios), post types, native-scheduling window, default cadence, first-comment support, and the `variants[].overrides` keys each platform requires or accepts. Read it before composing instead of discovering a limit by failing a publish. - get_agency (scope: agency.read) — The current workspace profile — name, plan, app mode, settings, and the caller’s role. - list_reports (scope: reports.read) — List the workspace's saved analytics report definitions. - get_best_times (scope: besttimes.read) — Worker-derived best posting times (strongest-first), optionally by client / platform. - list_approvals (scope: approvals.read) — List the workspace's approval queue (pending and decided requests), newest-first, with optional status / post filters. - get_posting_limits (scope: accounts.read) — Anti-ban posting limits (cadence): one account by socialAccountId, or every active channel plus the tenant bypass flag when omitted. - get_quota (scope: accounts.read) — Adaptive quota pool: your scarce-bucket budget (guaranteed slice, used, burst headroom, tomorrow’s guarantee tonight, reset clock) — read before planning a batch. - list_campaigns (scope: campaigns.read) — List the workspace's campaigns (named post groupings), optionally filtered by client / status. - decide_approval (scope: approvals.decide, WRITE — confirm with the user first) — Approve, reject, or request changes on a pending approval request (approve releases the gated post for publishing). A write action — confirm with the user before calling. - validate_post (scope: posts.compose) — Dry-run a compose request and get the per-platform verdict WITHOUT writing anything — same arguments as create_post, no post row and no draft. Returns `{ valid, platforms[], estimatedCostUsd }`. Does not run cadence or quota admission, so `valid: true` means the shape is acceptable, not that a publish cannot later be deferred. - revise_post (scope: posts.compose, WRITE — confirm with the user first) — Change the CONTENT of an already-scheduled post — caption, media, cover, alt text, per-platform overrides — without disturbing its destinations. Send only what you are changing. Refuses, naming them, when a destination has already published or is about to. An edit to an APPROVED post returns it to review. A write action — confirm with the user before calling. - create_post (scope: posts.compose, WRITE — confirm with the user first) — Compose a post and fan it out across the selected accounts (or save it as a draft). Retry-safe: retrying with identical arguments replays the original result instead of creating a duplicate. Note: two publish-now calls with byte-identical content and targets are treated as the same post and coalesced permanently — to intentionally publish the same content twice, vary the content or use distinct scheduled times. For platform-specific settings (e.g. YouTube privacy, TikTok privacy level, Pinterest board, Telegram chat), set `variants[].overrides`. That same object carries `firstComment`: a reply posted automatically once the destination goes live, so a "link in the comments" post needs no manual follow-up. Supported on X, LinkedIn, YouTube, Instagram, Facebook, Threads, Telegram and Bluesky (not TikTok or Pinterest); spell it exactly `firstComment`. Overrides are free-form — an unrecognised key is accepted and silently ignored — and the comment is best-effort: if it fails the post still publishes and nothing is reported, so confirm on the published post. A write action — confirm with the user before calling. - update_post (scope: posts.compose, WRITE — confirm with the user first) — Replace a draft post in place — its content, media, targets, and schedule. Only draft posts can be updated. Platform-specific settings live in `variants[].overrides`, same as create_post, including `firstComment`. A write action — confirm with the user before calling. - import_media (scope: media.manage, WRITE — confirm with the user first) — Ingest an external image / video into the content library by URL — the file must already be reachable at a public URL. For bytes you already hold (an image you just rendered, or a local file) use `upload_media` instead. A write action — confirm with the user before calling. - upload_media (scope: media.manage, WRITE — confirm with the user first) — Upload a LOCAL image / video into the content library and get back a media id for create_post. Sends the file inline (≤18 MiB) or returns a presigned URL for larger files. A write action — confirm with the user before calling. - reschedule_post (scope: posts.compose, WRITE — confirm with the user first) — Move a pending post target to a new time (absolute scheduledAt or relative shiftMinutes). A write action — confirm with the user before calling. - bulk_post_target_action (scope: posts.compose, WRITE — confirm with the user first) — Reschedule, retry or cancel up to 5000 post targets in one call — the batch form of reschedule_post / retry_post / cancel_post. Returns `{ jobId, accepted, skipped, skippedReasons? }`; a skipped target is reported, never silently dropped. A write action — confirm with the user before calling. - cancel_post (scope: posts.compose, WRITE — confirm with the user first) — Cancel a still-pending post target before it publishes. A write action — confirm with the user before calling. - retry_post (scope: posts.compose, WRITE — confirm with the user first) — Re-queue a failed or rate-limited post target so it publishes again. A write action — confirm with the user before calling. - set_post_recycle (scope: posts.compose, WRITE — confirm with the user first) — Set, update, or disable a post's evergreen recycle settings (interval / times / end date / max recycles). A write action — confirm with the user before calling. - shift_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Move a whole campaign's pending schedule by a relative number of minutes. A write action — confirm with the user before calling. - list_webhooks (scope: webhooks.read) — List the workspace's outbound webhook endpoints (url, subscribed events, active flag; secret masked). - create_webhook (scope: webhooks.manage, WRITE — confirm with the user first) — Register an outbound webhook endpoint for HMAC-signed event deliveries. Subscribe to any subset of the full 40-event catalog — see /developers/webhooks, or the `webhooks` block in openapi.json, for the complete list with payload fields. Returns the signing secret once. A write action — confirm with the user before calling. - delete_webhook (scope: webhooks.delete, WRITE — confirm with the user first) — Delete an outbound webhook endpoint by id (deliveries cascade). A write action — confirm with the user before calling. - create_client (scope: clients.manage, WRITE — confirm with the user first) — Create a new client group (a brand or influencer whose accounts are managed together). A write action — confirm with the user before calling. - create_connect_invite (scope: accounts.connect, WRITE — confirm with the user first) — Mint a shareable self-connect link so a client can connect the requested platforms themselves; returns the link and its expiry. A write action — confirm with the user before calling. - get_webhook (scope: webhooks.read) — Read one webhook endpoint — URL, subscribed events, active state, consecutive failures, and when/why it was auto-disabled. The signing secret is never returned here. - list_webhook_deliveries (scope: webhooks.read) — The delivery log for one endpoint — each attempt with its event type, response status, error and timing. How you find out WHY an endpoint stopped receiving. - update_webhook (scope: webhooks.manage, WRITE — confirm with the user first) — Change an endpoint’s URL, event list, or active state — including re-enabling one auto-disabled after repeated failures. The event list REPLACES the previous one. A write action — confirm with the user before calling. - test_webhook (scope: webhooks.manage, WRITE — confirm with the user first) — Send a `webhook.test` delivery and report the outcome, proving an endpoint is reachable and verifying signatures without waiting for a real event. Performs a real outbound request — confirm with the user before calling. - rotate_webhook_secret (scope: webhooks.manage, WRITE — confirm with the user first) — Mint a new signing secret and return it ONCE — it is not readable again. The previous secret keeps verifying for a short overlap so the customer can redeploy without dropping deliveries. A write action — confirm with the user before calling. - get_campaign (scope: campaigns.read) — Read one campaign in full — name, description, status, colour, date range, owning client and post counts. - create_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Create a campaign — a named group of posts with an optional client, colour and date range. Posts are added with assign_to_campaign. A write action — confirm with the user before calling. - update_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Change a campaign’s name, description, status, colour or date range. A write action — confirm with the user before calling. - delete_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Delete a campaign. The posts in it are NOT deleted — they lose the grouping and keep their own schedules. A write action — confirm with the user before calling. - assign_to_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Add up to 500 existing posts to a campaign. Grouping only — no post’s content or schedule changes. A write action — confirm with the user before calling. - unassign_from_campaign (scope: campaigns.manage, WRITE — confirm with the user first) — Remove posts from a campaign. Grouping only — the posts and their schedules are untouched. A write action — confirm with the user before calling. - get_client (scope: clients.read) — Read one client group in full — name, type, approval policy, posting rules, brand voice and default timezone. - update_client (scope: clients.manage, WRITE — confirm with the user first) — Change a client’s name, type, approval policy, posting rules, brand voice or default timezone. A write action — confirm with the user before calling. - delete_client (scope: clients.manage, WRITE — confirm with the user first) — Archive a client group. OWNER ONLY — a Manager holding the clients.manage scope cannot do this. A write action — confirm with the user before calling. - get_post_target (scope: posts.read) — Read a single fan-out destination in full — status, scheduled time, platform, account, published permalink and external id, and its failure reason when it failed. - get_post_recycle (scope: posts.read) — Read one post’s evergreen re-posting configuration and the worker’s bookkeeping. Returns null when the post is not enrolled. - get_account (scope: accounts.read) — Read one connected social account in full — platform, handle, display name, avatar, status (including whether it needs re-authorisation) and owning client. - get_approval (scope: approvals.read) — Read one approval request with the gated post’s preview and roll-up, so a reviewer can see what they are deciding on before calling decide_approval. - get_media_asset (scope: media.read) — Read one content-library asset in full — filename, kind, size, dimensions, duration, folder, labels, alt text, pipeline status, and the publishable/ready flags plus the per-platform readiness map. - get_client_analytics (scope: analytics.read) — One client group’s analytics — totals, a per-platform breakdown and a time series. A different projection from get_analytics_overview filtered by client. - get_report (scope: reports.read) — Read one report definition with its run history — each run’s status, window, and whether an export is available. - download_report_export (scope: reports.read) — Get a time-limited download URL for one completed report run’s export file. The URL expires — fetch it when you are ready to use it. - clear_post_recycle (scope: posts.compose, WRITE — confirm with the user first) — Turn OFF evergreen re-posting for a post. Idempotent. Already-scheduled recycled copies are not retracted — cancel those separately. A write action — confirm with the user before calling. ## Webhooks (push instead of poll) Register an endpoint with POST https://api.skedcast.com/v1/webhooks (scope webhooks.manage; the signing secret is returned once). Deliveries POST a JSON envelope { id, type, created, data }, signed with both the Standard Webhooks headers (webhook-id / webhook-timestamp / webhook-signature) and X-SkedCast-Signature (t=,v1=.">). At-least-once with exponential backoff — dedupe on the payload id. Naming is object.verb: past tense for outcomes, present participle for in-flight states, predictive for warnings. post.published / post.failed fire per DESTINATION; prefer their target.published / target.failed twins. Every event, with the data fields it carries: - post.scheduled — A compose committed and fanned out to one or more destinations. data: postId, status, targetCount, platforms - post.published — A destination published (per target — kept for compatibility; identical to target.published). data: postTargetId, platform, accountLabel, externalId, publishedAt - post.failed — A destination failed terminally (per target — kept for compatibility; identical to target.failed). data: postTargetId, platform, accountLabel, reason, category, failedAt - post.partially_published — The LAST destination of a post settled and the outcomes were mixed (at least one published, at least one failed). At most once per post. data: postId, publishedCount, failedCount, canceledCount, totalTargets, partiallyPublishedAt - post.canceled — Every destination of a post ended canceled, so nothing of it will publish. At most once per post. data: postId, canceledCount, totalTargets, reason, canceledAt - post.revised — An already-scheduled post had its content edited in place. `reGated` is true when the edit returned an approved post to review, in which case nothing publishes until it is approved again. data: postId, changed, reGated, destinationsTouched, destinationsAdded, destinationsRemoved, revisedAt - post.recycled — An evergreen post was recycled: a fresh copy was created and scheduled to publish again. `postId` is the new post; `sourcePostId` is the evergreen original. data: postId, sourcePostId, targetsCreated, scheduledAt - target.preparing — A destination is parked waiting for its media to finish preparing; it retries automatically. data: postTargetId, platform, accountLabel, status, statusReason, retryAfterMs, preparingAt - target.rate_limited — A destination was spaced out to protect the account (or by quota) and was rescheduled automatically. data: postTargetId, platform, accountLabel, status, retryAfterMs, quotaReason, rateLimitedAt - target.published — A destination published successfully. data: postTargetId, platform, accountLabel, externalId, publishedAt - target.failed — A destination failed terminally (including retry exhaustion). data: postTargetId, platform, accountLabel, reason, category, failedAt - target.canceled — A pending destination was canceled — by a member, a bulk action, or because its account was disconnected. data: postTargetId, postId, platform, reason, canceledAt - target.rescheduled — A pending destination was moved to a new instant. data: postTargetId, postId, platform, scheduledAt, rescheduledAt - media.ready — An asset finished scanning and transcoding and is publishable. data: assetId, status - media.failed — An asset could not be processed (unsupported, or un-encodable within budget) and will not be retried. data: assetId, status, reason - media.quarantined — An asset was refused as unsafe (malware, spoofed type) and will never be served. data: assetId, status, reason - media.deleted — An asset was moved to the recycle bin (soft-deleted); it purges after the plan window. data: assetId, purgeDueAt - media.variant.ready — A per-platform rendition of an asset is ready (at ingest, or from a publish-time fill). data: assetId, platform, status - media.variant.failed — A per-platform rendition could not be produced (for example, the source exceeds that platform's duration cap). The asset may still publish elsewhere. data: assetId, platform, status - account.connected — A channel was connected for the first time. data: accountId, platform, clientId, connectedVia, handle - account.reconnected — An existing channel was re-authorized (its credential refreshed or reclaimed). data: accountId, platform, clientId, connectedVia, handle - account.disconnected — A channel was disconnected — by a member, a whole-login disconnect, an operator, or a platform data-deletion request. data: accountId, platform, clientId, reason, disconnectedAt - account.token_expiring — Credentials on one or more channels are about to expire — one aggregated event per workspace per tick. data: count, accounts[{accountId, platform}] - account.reauth_required — A channel's credential is proven dead and publishing to it is paused until a person reconnects it. data: accountId, platform, clientId, source, reason - approval.requested — A post was submitted for review and is waiting on a reviewer. data: postId, status - approval.decided — A reviewer approved or rejected a post. data: postId, status, releasedTargets - approval.withdrawn — A pending review request was withdrawn — the author un-requested it, or its channel was disconnected. data: approvalId, postId, reason, withdrawnAt - analytics.updated — An account's analytics pull finished and its stored metrics moved — read them back now instead of polling. data: socialAccountId, platform, postsUpdated, daysUpdated, fetchedAt - report.completed — A report run rendered and its export is ready to download. data: reportId, runId, status - report.failed — A report run failed to render. data: reportId, runId, status, error - client.created — A client (workspace customer) was created. data: clientId, name, type - client.updated — A client's settings changed. data: clientId, changed - client.deleted — A client was archived (soft-deleted). data: clientId - campaign.created — A campaign was created. data: campaignId, name, status, clientId - campaign.updated — A campaign's fields changed. data: campaignId, changed - campaign.deleted — A campaign was deleted; its posts survive un-grouped. data: campaignId - campaign.shifted — A campaign's pending posts were rescheduled together by a fixed offset. data: campaignId, shiftMinutes, accepted, skipped - member.invited — A person was invited to the workspace. data: inviteId, email, role, expiresAt - member.joined — An invitee accepted and became a member. data: inviteId, memberId, role - member.removed — A member was removed from the workspace. data: memberUserId ## OAuth discovery URLs - https://api.skedcast.com/.well-known/oauth-authorization-server (RFC 8414) - https://api.skedcast.com/.well-known/oauth-protected-resource (RFC 9728) - https://api.skedcast.com/.well-known/oauth-protected-resource/mcp (RFC 9728 §3.1 — well-known segment before the /mcp resource path) - https://api.skedcast.com/.well-known/jwks.json - https://api.skedcast.com/oauth/authorize · https://api.skedcast.com/oauth/token · https://api.skedcast.com/oauth/register ## Documentation pages - Quickstart: https://skedcast.com/developers/quickstart — Make your first authenticated request to the SkedCast API in five minutes. - Authentication: https://skedcast.com/developers/authentication — Authenticate with an API key (Bearer) or the OAuth 2.1 flow for third-party apps. - Scopes & permissions: https://skedcast.com/developers/scopes — The programmatic scopes an API key, OAuth token, or MCP tool may hold — reads, the publish action, library writes, and webhook management. - Conventions: envelopes, pagination, errors: https://skedcast.com/developers/conventions — Response envelopes, keyset pagination, idempotency, correlation IDs, and the RFC 9457 error model. - REST API reference: https://skedcast.com/developers/api-reference — Every /v1 endpoint — posts, post targets, clients, accounts, agency, analytics, reports, best-times, media, and webhooks. - TypeScript SDK: https://skedcast.com/developers/sdk — The typed client that powers the SkedCast console — and, soon, a public npm package. - Connect an AI agent (MCP): https://skedcast.com/developers/mcp — Connect Claude (or any MCP client) to read your workspace and publish on your behalf. - Connect an app (OAuth 2.1): https://skedcast.com/developers/oauth — A worked OAuth 2.1 flow — dynamic registration, PKCE, the authorize redirect, token exchange, refresh, and revoke. - Partner onboarding: connect a client's accounts: https://skedcast.com/developers/partner-onboarding — Create a client, mint a connect link they open with no SkedCast login, then publish on their behalf via the API. - Uploading media & video covers: https://skedcast.com/developers/media-uploads — The real upload sequence — presign, PUT, register — plus how to attach a custom video cover and narrow which platform renditions get produced. - Webhooks: every event, signed and delivered: https://skedcast.com/developers/webhooks — Subscribe once, stop polling. All 40 events — publish outcomes per destination, media readiness and failures, account health, approvals, reports, team and client changes — HMAC-signed with retries and durable de-duplication. - Versioning & changelog: https://skedcast.com/developers/changelog — How the API is versioned and how breaking changes are handled.