POSTARD DEVELOPERS

REST API reference

The current REST contract for content drafts, workspace access, and the first social adapters.

Early API · v0.2. Use your configured API origin and a first-party app-session bearer token, or a customer key on explicitly supported endpoints. Keys require an active plan or trial. Scheduling needs the publishing switch, worker, social permissions, and entitlements. Publishing MCP tools remain planned. The authentication guide lists the exact customer-key boundaries.

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.

Download OpenAPI 3.1

Endpoints

Stripe billing webhook

POST/webhooks/stripe

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 · Success: 200

JSON request schema
Stripe billing webhook · Request schema
{
  "type": "object",
  "additionalProperties": true
}

Read billing availability

GET/v1/workspaces/{workspaceId}/billing

Returns plan, status, entitlement expiry, billing interval, cancellation state and enabled flag. Never returns Stripe credentials.

Permission: Owner or admin app session · Success: 200

Open hosted checkout

POST/v1/workspaces/{workspaceId}/billing/checkout

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 · Success: 200

JSON request schema
Open hosted checkout · 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

POST/v1/workspaces/{workspaceId}/billing/portal

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 · Success: 200

Read workflow analytics

GET/v1/workspaces/{workspaceId}/analytics

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 · Success: 200

List API key metadata

GET/v1/workspaces/{workspaceId}/keys

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 · Success: 200

Create API key

POST/v1/workspaces/{workspaceId}/keys

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 · Success: 201

JSON request schema
Create API key · 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

DELETE/v1/workspaces/{workspaceId}/keys/{id}

Immediately blocks future key requests. Repeated revocation is safe and does not reveal another workspace's keys.

Permission: Owner or admin app session · Success: 200

Read developer usage

GET/v1/workspaces/{workspaceId}/usage

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 · Success: 200

Check database and worker readiness

GET/health/ready

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 · Success: 200

Read your email preferences

GET/v1/me/notifications

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 · Success: 200

Save your email preferences

POST/v1/me/notifications

Updates your own preferences. Explicit weekly opt-in is required. Essential Supabase Auth messages are separate.

Permission: First-party app session; MCP tokens rejected · Success: 200

JSON request schema
Save your email preferences · 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

POST/v1/me/account-event

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 · Success: 200

Unsubscribe from one optional email category

POST/notifications/unsubscribe

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 · Success: 200

JSON request schema
Unsubscribe from one optional email category · Request schema
{
  "type": "object",
  "required": [
    "token"
  ],
  "properties": {
    "token": {
      "type": "string",
      "maxLength": 1024
    }
  }
}

Receive signed Resend delivery events

POST/webhooks/resend

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 · Success: 200

JSON request schema
Receive signed Resend delivery events · 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

GET/v1/workspaces/{workspaceId}/publishing

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 · Success: 200

Reserve an image upload

POST/v1/workspaces/{workspaceId}/media/uploads

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 · Success: 201

JSON request schema
Reserve an image upload · 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

POST/v1/workspaces/{workspaceId}/media/{id}/complete

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 · Success: 200

Preview a ready image

GET/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.

Permission: Workspace member · Success: 200

List calendar posts

GET/v1/workspaces/{workspaceId}/posts

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 · Success: 200

Save a publishing draft

POST/v1/workspaces/{workspaceId}/posts

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 · Success: 201

JSON request schema
Save a publishing draft · 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

GET/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.

Permission: Workspace member · Success: 200

Edit a saved post draft

POST/v1/workspaces/{workspaceId}/posts/{id}/edit

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 · Success: 200

JSON request schema
Edit a saved post draft · Request schema
{
  "allOf": [
    {
      "$ref": "#/components/schemas/PostInput"
    },
    {
      "$ref": "#/components/schemas/PostVersion"
    }
  ]
}

Confirm a post schedule

POST/v1/workspaces/{workspaceId}/posts/{id}/schedule

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 · Success: 200

JSON request schema
Confirm a post schedule · 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

POST/v1/workspaces/{workspaceId}/posts/{id}/cancel

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 · Success: 200

JSON request schema
Cancel a pending post · Request schema
{
  "type": "object",
  "required": [
    "version"
  ],
  "properties": {
    "version": {
      "type": "integer",
      "minimum": 1
    }
  }
}

Check service health

GET/health

Returns a non-sensitive service name and version. Does not check database or provider availability.

Permission: No authentication · Success: 200

List workspaces

GET/v1/workspaces

Returns the caller's workspaces, ordered by creation time. The workspace ID is required for workspace-scoped routes.

Permission: Workspace member · Success: 200

List recent drafts

GET/v1/workspaces/{workspaceId}/drafts

Returns up to 50 drafts, most recently updated first. This route currently has no pagination, search, or cursor parameters.

Permission: Workspace member · Success: 200

Create a draft

POST/v1/workspaces/{workspaceId}/drafts

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 · Success: 201

JSON request schema
Create a draft · 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

GET/v1/providers

Lists Instagram and Threads, their configured state, and current adapter capabilities. Configured does not establish provider approval or publishing support.

Permission: Authenticated app user · Success: 200

List connected accounts

GET/v1/workspaces/{workspaceId}/connections

Returns profile metadata and granted scopes. Social credentials are never returned.

Permission: Workspace member · Success: 200

Read saved account metrics

GET/v1/workspaces/{workspaceId}/connections/{id}/metrics

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 · Success: 200

Start social OAuth

POST/v1/workspaces/{workspaceId}/integrations/{provider}/start

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 · Success: 200

Complete social OAuth

POST/v1/integrations/{provider}/callback

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 · Success: 200

JSON request schema
Complete social OAuth · 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

POST/v1/workspaces/{workspaceId}/connections/{id}/sync

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 · Success: 200

Disconnect an account

DELETE/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.

Permission: Owner or admin · Success: 200

Errors and retries

Possible HTTP error statuses across these routes
StatusMeaning
400Invalid input, JSON, UUID, or expired/reused OAuth state.
401Missing, expired, or unacceptable bearer token.
403Insufficient role, rejected Origin, or rejected Host.
404Workspace or connection not found or not accessible.
409Connection limit, subscription check, or reconnection required.
413Request body exceeds 65,536 bytes (64 KiB).
429Too many requests. Pause and retry with backoff.
500Unexpected service error.
502Upstream provider could not complete the request.
503Service, 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.