REST API · Repository CLI · Local + hosted MCP

Social media publishing API.
REST, CLI, and MCP.

PostOnce.co is a social media publishing API and MCP server for creating drafts, connecting channels through a browser handoff, uploading media, and preparing human-reviewed campaigns. Every entry point uses the same validation, quota, queue, and per-destination delivery records as the dashboard.

One domain model

Build with HTTP. Hand it to an agent when useful.

Use the local stdio bridge for operator-controlled files and trusted automation, or connect a remote client to /mcp with workspace-bound OAuth. Both keep provider credentials outside model context and make browser review the default boundary before public delivery.

01 / HTTP

REST API

Use scoped keys to discover channels and media, prepare publish plans, create drafts, and read destination outcomes.

/api/v1
02 / Terminal

Repository CLI

Use the same API from scripts or an operator terminal with a ten-minute browser login and JSON-first output.

pnpm agent:cli

Default public-delivery path

Prepare → review → consume → observe.

  1. 1

    Prepare an immutable planContent, media, settings, targets, and schedule are frozen together.

  2. 2

    Review in the browserA signed-in workspace approver sees the exact public fan-out.

  3. 3

    Consume the plan onceThe caller submits only the approved plan ID and cannot alter it.

  4. 4

    Read every receiptResume by durable post ID until each destination resolves.

mcp.jsonHosted OAuth
{
  "mcpServers": {
    "postonce": {
      "url": "https://postonce.co/mcp"
    }
  }
}
The client opens browser authorization so a human can choose and approve one workspace.

Authentication

Scoped keys or workspace-bound OAuth.

Workspace owners and admins can grant read, connect, upload, draft, reviewed-publish, and direct-publish access independently. posts:write creates drafts. posts:publish prepares and consumes browser-approved publication plans. The separate posts:publish:direct scope bypasses that review and is never available to hosted MCP OAuth. Discovery uses channels:read and media:read; browser connection handoffs require channels:connect. Local API keys may separately receive media:write.

Authorization: Bearer po_live_••••••••••••••••
Authorization: Bearer <short-lived OAuth token>
Plan access

The v1 API and hosted MCP are available on Pro and Agency. Manual keys and hosted OAuth bindings share a ten-active-credential workspace cap.

GET /api/v1/channels · GET /api/v1/media

Discover safe destination and media IDs.

Use channels:read to list destinations, their plan and connector availability, supported capabilities, safe publishing defaults, and the browser connection mode for each supported provider. Use media:read to list ready media with bounded page and limit pagination plus optional q and type filters.

curl https://postonce.co/api/v1/channels \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

curl "https://postonce.co/api/v1/media?type=image&limit=30" \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

These GET endpoints are read-only. They never return platform credentials, OAuth grants, private storage object keys, or signed file URLs. Connection and upload mutations use the separate scopes below.

POST · GET /api/v1/channel-connections

Keep provider authorization in the browser.

Create a request with a provider advertised by GET /api/v1/channels. The response contains a short-lived browser URL and connection ID bound to the API key's exact workspace. The signed-in owner or admin completes OAuth consent or enters a manual provider credential only in PostOnce; when both Instagram authorization methods are configured, the browser also asks that human to choose Instagram Login or a Facebook-linked Page. The caller then polls the ID for completed and the connected channel IDs.

curl -X POST https://postonce.co/api/v1/channel-connections \
  -H "Authorization: Bearer $POSTONCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"provider":"youtube"}'

curl https://postonce.co/api/v1/channel-connections/$CONNECTION_ID \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

OAuth tokens, app passwords, bot tokens, webhooks, and other provider credentials remain encrypted in PostOnce and never enter the v1 response, CLI, MCP conversation, or model context.

POST /api/v1/media · PUT storage · POST complete

Upload bytes without proxying them through PostOnce.

Initiate with filename, MIME type, byte length, and optional alt text. PUT the raw file to the returned ten-minute URL using only its returned headers—never send the PostOnce bearer to storage. Then complete the media ID. PostOnce verifies size and MIME, copies the staging object behind an immutable server-only key, and returns a safe media DTO.

curl -X POST https://postonce.co/api/v1/media \
  -H "Authorization: Bearer $POSTONCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"launch.jpg","contentType":"image/jpeg","contentLength":12345,"altText":"Launch screen"}'

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  -H "Content-Length: 12345" \
  --upload-file ./launch.jpg

curl -X POST https://postonce.co/api/v1/media/$MEDIA_ID/complete \
  -H "Authorization: Bearer $POSTONCE_API_KEY" \
	  -H "Content-Type: application/json" \
	  -d '{}'

POST /api/v1/content/prepare · POST /api/v1/content/publish-plans

Turn one URL, Markdown document, or full text into exact destination versions.

Submit a public HTTPS URL or inline source, then choose explicit channel IDs or allConnected. URL fetching blocks private networks, revalidates every redirect, accepts text formats only, and applies bounded time and size limits. PostOnce resolves a title, format, copy, safe platform defaults, and media requirements for each eligible destination.

curl -X POST https://postonce.co/api/v1/content/publish-plans \
	  -H "Authorization: Bearer $POSTONCE_API_KEY" \
	  -H "Content-Type: application/json" \
	  -d '{
	    "source": {
	      "kind": "url",
	      "url": "https://example.com/articles/launch"
	    },
	    "allConnected": true,
	    "status": "queued",
	    "mediaAssetIds": [],
	    "idempotencyKey": "source-launch-2026-08-17"
	  }'

Use {"kind":"markdown","title":"…","content":"…"} or {"kind":"text","title":"…","content":"…"} for inline sources. The response includes every prepared target and an immutable pending plan with a browser review URL; it creates no post or provider-side mutation until an approver accepts the exact versions and the caller consumes the plan. PostOnce fingerprints the source request before fetching or resolving destinations, so an exact retry returns the original plan while a different request cannot reuse its idempotency key.

POST /api/v1/publish-plans

Approve the exact delivery, then consume it once.

Send a queued or scheduled payload with explicit targets and a stable idempotencyKey. PostOnce freezes content, the external account binding, settings, per-target media routing, the immutable file version, alt text, and schedule. Preparation creates no post, job, quota reservation, or provider call.

curl https://postonce.co/api/v1/publish-plans \
  -H "Authorization: Bearer $POSTONCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @publication.json

curl -X POST https://postonce.co/api/v1/publish-plans/$PLAN_ID/consume \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

A signed-in workspace approver must open the returned review URL first. Consumption accepts only the plan ID, revalidates every binding, and atomically creates the campaign and approval receipt.

POST /api/v1/posts

Create a draft or a trusted direct delivery.

An idempotencyKey is required. Drafts require posts:write. The example below saves a private draft without a connected account. Use a fresh idempotency key for each new draft. One-step queued and scheduled requests require posts:publish:direct; ordinary agents should use publication plans.

curl https://postonce.co/api/v1/posts \
  -H "Authorization: Bearer $POSTONCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Launch note",
    "content": "The release is live.",
    "contentType": "post",
    "status": "draft",
    "timezone": "Asia/Taipei",
    "targets": [],
    "mediaAssetIds": [],
    "idempotencyKey": "release-2026-08-03-v1"
  }'

Destination IDs must belong to the key's workspace, be active, and match the submitted platform. Media must be fully uploaded to that workspace before it can be attached.

GET /api/v1/posts · GET /api/v1/posts/:id

Read the campaign and each destination state.

curl "https://postonce.co/api/v1/posts?limit=30&status=published" \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

curl "https://postonce.co/api/v1/posts/$POST_ID" \
  -H "Authorization: Bearer $POSTONCE_API_KEY"

Both routes require posts:read. For the next list page, read meta.nextCursor from the response and send it unchanged as ?cursor=…. The legacy before ISO timestamp is deprecated and only provides best-effort pagination. Post detail includes safe media metadata and destination delivery state without private storage keys or provider checkpoint data.

Workflow source webhook

Turn an external source into a governed campaign.

Each webhook workflow receives a random secret that is displayed only when created or rotated. Send it as a Bearer credential to the workflow endpoint. Strict filters and transform rules are stored with the workflow; only active destinations in the same workspace are accepted.

curl https://postonce.co/api/webhooks/workflows/$WORKFLOW_ID \
  -H "Authorization: Bearer $WORKFLOW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "cms-post-1842-published",
    "title": "A durable distribution system",
    "content": "The new article is available.",
    "contentType": "article",
    "sourceUrl": "https://example.com/articles/distribution",
    "tags": ["operations", "publishing"]
  }'

Use a stable eventId. The workflow derives an idempotency key from it so duplicate deliveries from the source do not create duplicate campaigns.

Delivery contract

Independent destinations, explicit uncertainty.

  • One successful destination delivery consumes one publish allowance.
  • Rate limits and known-idempotent temporary failures use bounded exponential retry.
  • Authentication failures stop and mark the channel for reconnection.
  • Ambiguous outcomes are held for review instead of blindly replayed.
  • A campaign can finish as published, partial, or failed without hiding the destination result.
Versioning

The stable surface lives under /api/v1. Workflow source webhooks are versioned by their stored workflow schema. Breaking public API changes require a new version.