POSTARD DEVELOPERS
REST API reference
The current REST contract for content drafts, workspace access, and the first social adapters.
All examples use JSON. Business responses use { "data": ... }; errors use { "error": "..." }, optionally with a provider code. The health endpoint has its own shape. Workspace and connection IDs are UUIDs. Provider is instagram or threads.
Endpoints
Stripe billing webhook
/webhooks/stripeSigned Stripe events reconcile canonical subscription state under a per-workspace lease. Disabled until the separate Postard Stripe account is configured. Raw body signature required.
JSON request schema
{
"type": "object",
"additionalProperties": true
}Read billing availability
/v1/workspaces/{workspaceId}/billingReturns plan, status, entitlement expiry, billing interval, cancellation state and enabled flag. Never returns Stripe credentials.
Open hosted checkout
/v1/workspaces/{workspaceId}/billing/checkoutOne pending Checkout per workspace. Eligible Solo owners receive one card-required seven-day trial. Disabled by default. Returns a Stripe-hosted URL; the return URL does not grant access.
JSON request schema
{
"type": "object",
"required": [
"plan",
"interval"
],
"properties": {
"plan": {
"enum": [
"starter",
"creator",
"agency"
]
},
"interval": {
"enum": [
"monthly",
"annual",
"semiannual"
]
}
},
"description": "Agency accepts monthly only. All prices come from the server catalog."
}Open billing portal
/v1/workspaces/{workspaceId}/billing/portalReturns a short-lived Stripe portal URL for the owner to manage card, invoices and renewal. Requires billing enabled and a customer mapping.
Read workflow analytics
/v1/workspaces/{workspaceId}/analyticsCurrent outcomes for posts created in a half-open UTC range, daily creation totals, per-network target outcomes and dated profile snapshots. Profile counts are not post engagement.
List API key metadata
/v1/workspaces/{workspaceId}/keysSafe prefixes, names, scopes, creation/use/revocation dates and whether each key fits the plan limit. No digest or secret is returned.
Create API key
/v1/workspaces/{workspaceId}/keysReturns one 256-bit secret once. Active unexpired plan required. Workspace caps: Solo 1, Creator 5, Agency unlimited. workspace:read must be included; drafts:write is optional.
JSON request schema
{
"type": "object",
"required": [
"name",
"scopes"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 60
},
"scopes": {
"type": "array",
"items": {
"enum": [
"workspace:read",
"drafts:write"
]
},
"minItems": 1,
"maxItems": 2
}
}
}Revoke API key
/v1/workspaces/{workspaceId}/keys/{id}Immediately blocks future key requests. Repeated revocation is safe and does not reveal another workspace's keys.
Read developer usage
/v1/workspaces/{workspaceId}/usageSeparate API/MCP successful-call counters for the current UTC month, plan limits and expiry. Overage is always disabled in this release.
Check database and worker readiness
/health/readyPublic, briefly cached dependency check. 503 means database access could not be verified. Worker disabled is explicit; this is not a live publishing test.
Read your email preferences
/v1/me/notificationsReads only the signed-in user’s weekly, operational, and sign-in preferences. Weekly updates default off.
Save your email preferences
/v1/me/notificationsUpdates your own preferences. Explicit weekly opt-in is required. Essential Supabase Auth messages are separate.
JSON request schema
{
"type": "object",
"required": [
"weekly_updates",
"product_notifications",
"login_alerts"
],
"properties": {
"weekly_updates": {
"type": "boolean"
},
"product_notifications": {
"type": "boolean"
},
"login_alerts": {
"type": "boolean"
}
}
}Observe a verified account session
/v1/me/account-eventFirst-party auth completion hook. Actor, session ID, and issue time come from the verified token, never request input. Deduplicates welcome and per-session sign-in notices; old sessions cannot forge a fresh event.
Unsubscribe from one optional email category
/notifications/unsubscribeRequires a valid signed category-specific token, not an app bearer. Use a JSON token body for the app flow. RFC 8058 one-click clients POST with token in the query and List-Unsubscribe=One-Click form data. GET does not unsubscribe. This cannot disable account confirmation/recovery.
JSON request schema
{
"type": "object",
"required": [
"token"
],
"properties": {
"token": {
"type": "string",
"maxLength": 1024
}
}
}Receive signed Resend delivery events
/webhooks/resendOperator integration only. Requires svix-id, svix-timestamp and svix-signature over the exact raw body using the configured webhook secret. Records deduplicated delivered/failed/bounced/complained events; bounces/complaints suppress recipients. No app-session auth substitutes for the signature.
JSON request schema
{
"type": "object",
"required": [
"type",
"created_at",
"data"
],
"properties": {
"type": {
"enum": [
"email.delivered",
"email.failed",
"email.bounced",
"email.complained"
]
},
"created_at": {
"type": "string",
"format": "date-time"
},
"data": {
"type": "object",
"required": [
"email_id"
],
"properties": {
"email_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"to": {
"type": "array",
"maxItems": 50,
"items": {
"type": "string",
"format": "email"
}
}
},
"additionalProperties": true
}
}
}Check publishing setup
/v1/workspaces/{workspaceId}/publishingReports the publishing switch, recent worker heartbeat, provider configuration and initial formats. This is setup state, not proof of live Meta approval.
Reserve an image upload
/v1/workspaces/{workspaceId}/media/uploadsReserves a JPEG/PNG/WebP upload up to 20 MiB and returns its path-bound Storage upload token. Upload bytes directly with Supabase uploadToSignedUrl, then call complete. The development guardrails are 100 reservations/day and a 2 GiB recorded-media limit per workspace, not paid plan entitlements.
JSON request schema
{
"type": "object",
"required": [
"file_name",
"mime_type",
"size_bytes"
],
"properties": {
"file_name": {
"type": "string",
"minLength": 1,
"maxLength": 180
},
"mime_type": {
"enum": [
"image/jpeg",
"image/png",
"image/webp"
]
},
"size_bytes": {
"type": "integer",
"minimum": 1,
"maximum": 20971520
}
}
}Validate and prepare an image
/v1/workspaces/{workspaceId}/media/{id}/completeDecodes actual bytes, rejects malformed/animated images, strips metadata and produces a private JPEG. Use images at least 320px wide, with aspect ratio 4:5–1.91:1. Output is bounded to 1440×1800 and 8 MiB. Only ready images can be attached to posts. No video/carousel processing yet.
Preview a ready image
/v1/workspaces/{workspaceId}/media/{id}Returns image metadata and a 10-minute signed preview URL. A reviewed image cannot be overwritten through browser Storage access.
List calendar posts
/v1/workspaces/{workspaceId}/postsReturns up to 200 posts in a range of at most 62 days. Scheduled posts match scheduled_for; unscheduled drafts match created_at. Ordered by created_at descending. No cursor pagination yet.
Save a publishing draft
/v1/workspaces/{workspaceId}/postsSaves content, one optional ready image and up to 20 profiles in the same workspace. Repeating an identical request_id/payload returns the same post. Reusing it for different content is rejected. Saving does not publish.
JSON request schema
{
"type": "object",
"required": [
"request_id",
"title",
"content"
],
"properties": {
"request_id": {
"type": "string",
"format": "uuid"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"content": {
"type": "string",
"minLength": 1,
"maxLength": 10000
},
"media_asset_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"connection_ids": {
"type": "array",
"maxItems": 20,
"uniqueItems": true,
"items": {
"type": "string",
"format": "uuid"
}
}
},
"description": "Creates a saved post draft, not a delivery. Reuse the request_id only when retrying the same create payload."
}Read a post and its delivery state
/v1/workspaces/{workspaceId}/posts/{id}Includes per-profile status, published ID/time, and safe error codes. needs_review means a public write's outcome is uncertain: inspect the platform before attempting a new post.
Edit a saved post draft
/v1/workspaces/{workspaceId}/posts/{id}/editOnly draft posts can be edited. Requires the current version. Queued/delivering posts or stale versions return 409. Targets and image references are checked against the workspace.
JSON request schema
{
"allOf": [
{
"$ref": "#/components/schemas/PostInput"
},
{
"$ref": "#/components/schemas/PostVersion"
}
]
}Confirm a post schedule
/v1/workspaces/{workspaceId}/posts/{id}/scheduleRequires explicit user intent, a current version, one or more permitted profiles, publishing enabled, a recent worker heartbeat and active/trialing entitlements. Accepts now through one year ahead. Instagram needs a ready image and caption ≤2200 characters; Threads text is ≤500. Rescheduling replaces the revision; old queued jobs cannot publish. No publishing MCP tool is exposed. Live provider setup must be verified separately.
JSON request schema
{
"allOf": [
{
"$ref": "#/components/schemas/PostVersion"
},
{
"type": "object",
"required": [
"scheduled_for",
"timezone"
],
"properties": {
"scheduled_for": {
"type": "string",
"format": "date-time"
},
"timezone": {
"type": "string",
"maxLength": 80,
"description": "IANA timezone, for example Europe/Lisbon. Send an explicit UTC/offset timestamp; never an ambiguous wall-clock value."
}
}
}
]
}Cancel a pending post
/v1/workspaces/{workspaceId}/posts/{id}/cancelCancels only a draft or a schedule whose delivery has not started. Current version required. In-flight/finished/uncertain deliveries cannot be cancelled or automatically retried through this route.
JSON request schema
{
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "integer",
"minimum": 1
}
}
}Check service health
/healthReturns a non-sensitive service name and version. Does not check database or provider availability.
List workspaces
/v1/workspacesReturns the caller's workspaces, ordered by creation time. The workspace ID is required for workspace-scoped routes.
List recent drafts
/v1/workspaces/{workspaceId}/draftsReturns up to 50 drafts, most recently updated first. This route currently has no pagination, search, or cursor parameters.
Create a draft
/v1/workspaces/{workspaceId}/draftsSaves a text draft. Title: 1–120 characters; content: 1–10,000 characters, after trimming. Does not publish or schedule. Customer-key POSTs require a UUID Idempotency-Key and replay successful saves safely. App-session draft POSTs do not use this replay mechanism.
JSON request schema
{
"type": "object",
"required": [
"title",
"content"
],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"content": {
"type": "string",
"minLength": 1,
"maxLength": 10000
}
},
"description": "Title and content are trimmed before validation. Whitespace-only values are rejected."
}Check provider setup
/v1/providersLists Instagram and Threads, their configured state, and current adapter capabilities. Configured does not establish provider approval or publishing support.
List connected accounts
/v1/workspaces/{workspaceId}/connectionsReturns profile metadata and granted scopes. Social credentials are never returned.
Read saved account metrics
/v1/workspaces/{workspaceId}/connections/{id}/metricsReturns a stored snapshot with source and synced_at, or available: false with a reason. A missing snapshot is not zero performance. Provider metrics depend on granted permissions.
Start social OAuth
/v1/workspaces/{workspaceId}/integrations/{provider}/startCreates user-bound, one-time OAuth state and returns the provider authorization URL. State expires after 10 minutes. Requires server-side provider credentials; otherwise returns 503.
Complete social OAuth
/v1/integrations/{provider}/callbackThe web callback forwards the provider's code and state with the initiating user's session token. Consumes state once, exchanges credentials, and stores them encrypted. Do not forward codes to third parties or log request bodies. Profile and subscription checks may return 409.
JSON request schema
{
"type": "object",
"required": [
"code",
"state"
],
"properties": {
"code": {
"type": "string",
"minLength": 1,
"maxLength": 4096
},
"state": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{43}$"
}
}
}Refresh account metrics
/v1/workspaces/{workspaceId}/connections/{id}/syncFetches supported metrics from the configured provider and saves a dated snapshot. Expired or missing credentials require reconnection (409). No automatic background refresh is active.
Disconnect an account
/v1/workspaces/{workspaceId}/connections/{id}Removes the local connection and stored credentials. The user can separately revoke the provider-side grant in Instagram or Threads settings. A repeated deletion succeeds even if no local row remains.
Errors and retries
| Status | Meaning |
|---|---|
400 | Invalid input, JSON, UUID, or expired/reused OAuth state. |
401 | Missing, expired, or unacceptable bearer token. |
403 | Insufficient role, rejected Origin, or rejected Host. |
404 | Workspace or connection not found or not accessible. |
409 | Connection limit, subscription check, or reconnection required. |
413 | Request body exceeds 65,536 bytes (64 KiB). |
429 | Too many requests. Pause and retry with backoff. |
500 | Unexpected service error. |
502 | Upstream provider could not complete the request. |
503 | Service, storage operation, or provider configuration unavailable. |
Refresh an expired app session through Supabase. For 429 or temporary service failures, use bounded exponential backoff with jitter. Retrying the older text-draft POST may create another draft. The publishing-post create route deduplicates the same request UUID and payload; reusing that UUID with different content returns 409. Edits, scheduling, and cancellation require the saved version. Rate-limit reset headers are not implemented yet. A delivery marked for review must be checked on the social platform before another publication is attempted.
Transport and response details
Requests have a 64 KiB body limit. Responses use Cache-Control: no-store and X-Content-Type-Options: nosniff. Routes reached after body-size validation include an X-Request-Id for troubleshooting; a body-limit rejection may occur before that header is set.
Use HTTPS outside local development. The API validates its configured Host and permitted Origins. The web origin is the configured CORS origin. Arbitrary browser origins are not supported; server integrations should omit the Origin header rather than invent a browser origin.
See usage limits and authentication before building a production integration.