{"openapi":"3.1.0","info":{"title":"Postard REST API","version":"0.2.0","description":"Early API reference. First-party app-session JWTs support all dashboard routes. Scoped customer API keys support workspace, draft, profile and saved-metric routes only. API-key draft creation requires a UUID Idempotency-Key. Media and scheduling endpoints require current migrations. Live publishing is off by default and requires a configured background worker and approved provider access."},"servers":[{"url":"{apiOrigin}","variables":{"apiOrigin":{"default":"https://api.postard.app","description":"The API service origin, not the Next.js website. Replace for your environment."}}}],"security":[{"AppSession":[]}],"paths":{"/webhooks/stripe":{"post":{"operationId":"stripe-webhook","summary":"Stripe billing webhook","description":"Signed Stripe events reconcile canonical subscription state under a per-workspace lease. Disabled until the separate Postard Stripe account is configured. Raw body signature required. Permission: Stripe signature.","security":[],"parameters":[{"name":"stripe-signature","required":true,"description":"Stripe raw-body webhook signature","schema":{"type":"string"},"in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenericObject"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResult"}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/billing":{"get":{"operationId":"billing","summary":"Read billing availability","description":"Returns plan, status, entitlement expiry, billing interval, cancellation state and enabled flag. Never returns Stripe credentials. Permission: Owner or admin app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/billing/checkout":{"post":{"operationId":"checkout","summary":"Open hosted checkout","description":"One 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. Permission: Owner app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/billing/portal":{"post":{"operationId":"billing-portal","summary":"Open billing portal","description":"Returns a short-lived Stripe portal URL for the owner to manage card, invoices and renewal. Requires billing enabled and a customer mapping. Permission: Owner app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/analytics":{"get":{"operationId":"analytics","summary":"Read workflow analytics","description":"Current 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. Permission: Workspace member app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","required":true,"description":"Inclusive range start","schema":{"type":"string","format":"date-time"},"in":"query"},{"name":"to","required":true,"description":"Exclusive range end; at most 92 days after start","schema":{"type":"string","format":"date-time"},"in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/keys":{"get":{"operationId":"list-keys","summary":"List API key metadata","description":"Safe prefixes, names, scopes, creation/use/revocation dates and whether each key fits the plan limit. No digest or secret is returned. Permission: Owner or admin app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GenericObject"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create-key","summary":"Create API key","description":"Returns 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. Permission: Owner or admin app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyInput"}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/keys/{id}":{"delete":{"operationId":"revoke-key","summary":"Revoke API key","description":"Immediately blocks future key requests. Repeated revocation is safe and does not reveal another workspace's keys. Permission: Owner or admin app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/usage":{"get":{"operationId":"developer-usage","summary":"Read developer usage","description":"Separate API/MCP successful-call counters for the current UTC month, plan limits and expiry. Overage is always disabled in this release. Permission: Owner or admin app session.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/GenericObject"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health/ready":{"get":{"operationId":"backend-readiness","summary":"Check database and worker readiness","description":"Public, briefly cached dependency check. 503 means database access could not be verified. Worker disabled is explicit; this is not a live publishing test. Permission: Public.","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackendHealth"}}}},"503":{"description":"Database readiness could not be verified; component states are returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackendHealth"}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/me/notifications":{"get":{"operationId":"email-preferences","summary":"Read your email preferences","description":"Reads only the signed-in user’s weekly, operational, and sign-in preferences. Weekly updates default off. Permission: First-party app session; MCP tokens rejected.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/NotificationPreferences"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"save-email-preferences","summary":"Save your email preferences","description":"Updates your own preferences. Explicit weekly opt-in is required. Essential Supabase Auth messages are separate. Permission: First-party app session; MCP tokens rejected.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferences"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/NotificationPreferences"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/me/account-event":{"post":{"operationId":"observe-account-session","summary":"Observe a verified account session","description":"First-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. Permission: Fresh first-party session.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AccountEvent"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/unsubscribe":{"post":{"operationId":"unsubscribe-email","summary":"Unsubscribe from one optional email category","description":"Requires 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. Permission: Signed unsubscribe token.","security":[],"parameters":[{"name":"token","required":false,"description":"Signed category token for one-click clients; otherwise supply it in the JSON body.","schema":{"type":"string","maxLength":1024},"in":"query"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsubscribeInput"}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"List-Unsubscribe":{"const":"One-Click"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsubscribeResult"}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/resend":{"post":{"operationId":"resend-webhook","summary":"Receive signed Resend delivery events","description":"Operator 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. Permission: Verified Resend webhook signature.","security":[],"parameters":[{"name":"svix-id","required":true,"description":"Resend/Svix signed delivery webhook header.","schema":{"type":"string"},"in":"header"},{"name":"svix-timestamp","required":true,"description":"Resend/Svix signed delivery webhook header.","schema":{"type":"string"},"in":"header"},{"name":"svix-signature","required":true,"description":"Resend/Svix signed delivery webhook header.","schema":{"type":"string"},"in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResult"}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/publishing":{"get":{"operationId":"publishing-setup","summary":"Check publishing setup","description":"Reports the publishing switch, recent worker heartbeat, provider configuration and initial formats. This is setup state, not proof of live Meta approval. Permission: Workspace member.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PublishingState"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/media/uploads":{"post":{"operationId":"reserve-image","summary":"Reserve an image upload","description":"Reserves 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUploadInput"}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MediaUpload"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/media/{id}/complete":{"post":{"operationId":"complete-image","summary":"Validate and prepare an image","description":"Decodes 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MediaAsset"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/media/{id}":{"get":{"operationId":"preview-image","summary":"Preview a ready image","description":"Returns image metadata and a 10-minute signed preview URL. A reviewed image cannot be overwritten through browser Storage access. Permission: Workspace member.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MediaAsset"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/posts":{"get":{"operationId":"calendar-posts","summary":"List calendar posts","description":"Returns 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. Permission: Workspace member.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","required":true,"description":"Inclusive ISO timestamp.","schema":{"type":"string","format":"date-time"},"in":"query"},{"name":"to","required":true,"description":"Exclusive ISO timestamp; within 62 days after from.","schema":{"type":"string","format":"date-time"},"in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PostView"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"save-post","summary":"Save a publishing draft","description":"Saves 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostInput"}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PostView"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/posts/{id}":{"get":{"operationId":"read-post","summary":"Read a post and its delivery state","description":"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. Permission: Workspace member.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PostView"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/posts/{id}/edit":{"post":{"operationId":"edit-post","summary":"Edit a saved post draft","description":"Only 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostEditInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PostView"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/posts/{id}/schedule":{"post":{"operationId":"schedule-post","summary":"Confirm a post schedule","description":"Requires 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PostRecord"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/posts/{id}/cancel":{"post":{"operationId":"cancel-post","summary":"Cancel a pending post","description":"Cancels 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. Permission: Owner, admin, or editor.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostVersion"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PostRecord"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"operationId":"health","summary":"Check service health","description":"Returns a non-sensitive service name and version. Does not check database or provider availability. Permission: No authentication.","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces":{"get":{"operationId":"workspaces","summary":"List workspaces","description":"Returns the caller's workspaces, ordered by creation time. The workspace ID is required for workspace-scoped routes. Permission: Workspace member.","security":[{"AppSession":[]},{"WorkspaceKey":[]}],"parameters":[{"name":"X-Postard-Workspace","in":"header","required":false,"description":"Workspace UUID required for API-key workspace discovery.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/drafts":{"get":{"operationId":"drafts","summary":"List recent drafts","description":"Returns up to 50 drafts, most recently updated first. This route currently has no pagination, search, or cursor parameters. Permission: Workspace member.","security":[{"AppSession":[]},{"WorkspaceKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Draft"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create-draft","summary":"Create a draft","description":"Saves 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. Permission: Owner, admin, or editor.","security":[{"AppSession":[]},{"WorkspaceKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"UUID required when authenticating with a customer API key. Reuse only for identical input retries.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftInput"}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CreatedDraft"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/providers":{"get":{"operationId":"providers","summary":"Check provider setup","description":"Lists Instagram and Threads, their configured state, and current adapter capabilities. Configured does not establish provider approval or publishing support. Permission: Authenticated app user.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Provider"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/connections":{"get":{"operationId":"connections","summary":"List connected accounts","description":"Returns profile metadata and granted scopes. Social credentials are never returned. Permission: Workspace member.","security":[{"AppSession":[]},{"WorkspaceKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Connection"}}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/connections/{id}/metrics":{"get":{"operationId":"metrics","summary":"Read saved account metrics","description":"Returns 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. Permission: Workspace member.","security":[{"AppSession":[]},{"WorkspaceKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Performance"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/integrations/{provider}/start":{"post":{"operationId":"oauth-start","summary":"Start social OAuth","description":"Creates 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. Permission: Owner or admin.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["instagram","threads"]}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OAuthStart"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/integrations/{provider}/callback":{"post":{"operationId":"oauth-callback","summary":"Complete social OAuth","description":"The 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. Permission: Initiating user; workspace owner or admin.","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["instagram","threads"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallback"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ConnectionSaved"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/connections/{id}/sync":{"post":{"operationId":"sync","summary":"Refresh account metrics","description":"Fetches supported metrics from the configured provider and saves a dated snapshot. Expired or missing credentials require reconnection (409). No automatic background refresh is active. Permission: Owner or admin.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Performance"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}/connections/{id}":{"delete":{"operationId":"disconnect","summary":"Disconnect an account","description":"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. Permission: Owner or admin.","parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Disconnected"}}}}}},"default":{"description":"JSON error; see the public reference for status codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"WorkspaceKey":{"type":"http","scheme":"bearer","bearerFormat":"psk_…","description":"Workspace API key, shown once. Active plan/trial required. Only operations explicitly listing this scheme accept keys."},"AppSession":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"First-party Supabase app-session access token. MCP OAuth tokens and service-role keys are not accepted as app sessions."}},"schemas":{"GenericObject":{"type":"object","additionalProperties":true},"ApiKeyInput":{"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}}},"CheckoutInput":{"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."},"NotificationPreferences":{"type":"object","required":["weekly_updates","product_notifications","login_alerts"],"properties":{"weekly_updates":{"type":"boolean"},"product_notifications":{"type":"boolean"},"login_alerts":{"type":"boolean"}}},"AccountEvent":{"type":"object","properties":{"queued":{"type":"boolean"}}},"BackendHealth":{"type":"object","required":["service","database","worker","checked_at"],"properties":{"service":{"const":"postard-backend"},"database":{"enum":["operational","unavailable"]},"worker":{"enum":["online","offline","disabled"]},"checked_at":{"type":"string","format":"date-time"}}},"UnsubscribeInput":{"type":"object","required":["token"],"properties":{"token":{"type":"string","maxLength":1024}}},"UnsubscribeResult":{"type":"object","properties":{"unsubscribed":{"type":"boolean"}}},"WebhookEvent":{"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}}},"WebhookResult":{"type":"object","properties":{"received":{"type":"boolean"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Optional provider error code."}}},"Health":{"type":"object","required":["status","service","version"],"properties":{"status":{"const":"ok"},"service":{"const":"postard-api"},"version":{"type":"string"}}},"Workspace":{"type":"object","required":["id","name","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"DraftInput":{"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."},"CreatedDraft":{"type":"object","required":["id","title","content","version"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"version":{"type":"integer"}}},"Draft":{"allOf":[{"$ref":"#/components/schemas/CreatedDraft"},{"type":"object","required":["updated_at"],"properties":{"updated_at":{"type":"string","format":"date-time"}}}]},"Connection":{"type":"object","required":["id","provider","username","status","scopes","expires_at","connected_at"],"properties":{"id":{"type":"string","format":"uuid"},"provider":{"enum":["instagram","threads"]},"username":{"type":"string"},"status":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"expires_at":{"type":["string","null"],"format":"date-time"},"connected_at":{"type":"string","format":"date-time"}}},"Performance":{"oneOf":[{"type":"object","required":["available","metrics","source","synced_at"],"properties":{"available":{"const":true},"metrics":{"type":"object","additionalProperties":true},"source":{"type":"string"},"synced_at":{"type":"string","format":"date-time"}}},{"type":"object","required":["available","reason"],"properties":{"available":{"const":false},"reason":{"type":"string"}}}]},"Provider":{"type":"object","required":["provider","configured","capabilities"],"properties":{"provider":{"enum":["instagram","threads"]},"configured":{"type":"boolean"},"capabilities":{"type":"array","items":{"type":"string"}}}},"OAuthStart":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}},"OAuthCallback":{"type":"object","required":["code","state"],"properties":{"code":{"type":"string","minLength":1,"maxLength":4096},"state":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"}}},"ConnectionSaved":{"type":"object","required":["id","workspace_id"],"properties":{"id":{"type":"string","format":"uuid"},"workspace_id":{"type":"string","format":"uuid"}}},"Disconnected":{"type":"object","required":["disconnected"],"properties":{"disconnected":{"const":true}}},"MediaUploadInput":{"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}}},"MediaUpload":{"type":"object","required":["id","path","token"],"properties":{"id":{"type":"string","format":"uuid"},"path":{"type":"string"},"token":{"type":"string","description":"Path-bound signed upload token. Treat as a credential; do not log or share it with an AI client."}}},"MediaAsset":{"type":"object","required":["id","file_name","mime_type","status","preview_url"],"properties":{"id":{"type":"string","format":"uuid"},"file_name":{"type":"string"},"mime_type":{"const":"image/jpeg"},"size_bytes":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"status":{"const":"ready"},"preview_url":{"type":"string","format":"uri","description":"Private signed preview URL valid for 10 minutes."}}},"PostInput":{"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."},"PostEditInput":{"allOf":[{"$ref":"#/components/schemas/PostInput"},{"$ref":"#/components/schemas/PostVersion"}]},"PostVersion":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","minimum":1}}},"ScheduleInput":{"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."}}}]},"PostRecord":{"type":"object","required":["id","workspace_id","title","content","version","status","timezone"],"properties":{"id":{"type":"string","format":"uuid"},"workspace_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"version":{"type":"integer"},"media_asset_id":{"type":["string","null"],"format":"uuid"},"scheduled_for":{"type":["string","null"],"format":"date-time"},"timezone":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"status":{"enum":["draft","scheduled","publishing","published","partial","failed","cancelled","needs_review"]}}},"PostTarget":{"type":"object","required":["id","provider","profile_name","status"],"properties":{"id":{"type":"string","format":"uuid"},"connection_id":{"type":["string","null"],"format":"uuid"},"provider":{"enum":["instagram","threads"]},"profile_name":{"type":"string"},"status":{"enum":["draft","pending","processing","retry","published","failed","cancelled","needs_review"]},"error_code":{"type":["string","null"]},"published_id":{"type":["string","null"]},"published_at":{"type":["string","null"],"format":"date-time"}}},"PostView":{"allOf":[{"$ref":"#/components/schemas/PostRecord"},{"type":"object","required":["post_targets"],"properties":{"post_targets":{"type":"array","items":{"$ref":"#/components/schemas/PostTarget"}}}}]},"PublishingState":{"type":"object","required":["enabled","worker_online","providers","formats"],"properties":{"enabled":{"type":"boolean"},"worker_online":{"type":"boolean"},"providers":{"type":"array","items":{"type":"object","properties":{"provider":{"enum":["instagram","threads"]},"configured":{"type":"boolean"}}}},"formats":{"type":"array","items":{"type":"string"}}}}}}}