Authentication

API keys, the base URL, the approval gate and what is dashboard-only.

API keys

  • Base URL: https://postgale.com/api/v1. JSON in, JSON out.
  • Authentication: Authorization: Bearer sk_… with a key from Dashboard → API keys. A key acts for its organization only; ids that belong to another organization answer 404.
  • API keys (/keys) and organization settings (/org-settings) are dashboard-session only.
  • The same key works on the MCP server, with the same permissions, rate limit and approval rules.
GET /api/v1/accounts
curl https://postgale.com/api/v1/accounts \
  -H 'Authorization: Bearer sk_…'

Approval

When the organization, an account or the key requires approval, new posts are held as pending_approval until a person approves them in the dashboard (/posts/{id}/approve and /reject always answer 403 to API keys).

While approval applies, the actions an agent takes on someone else's behalf are queued for a person instead of running: comment replies and moderation (comment, reply, hide, unhide, delete, like), direct messages and WhatsApp sends, changes to published posts (edit, unpublish, comment settings), and, once X launches, X engagement and starting X conversations. The request is validated first (ownership, input, platform support, the messaging window), then answered with 202 and nothing reaches the platform yet:

202 Accepted
{
  "id": "cmact0a1b0001x2y3z4",
  "status": "pending_approval",
  "kind": "comment.reply",
  "accountIds": [
    "acc_instagram"
  ],
  "payloadSha256": "5f2c…",
  "createdAt": "2026-10-19T14:02:11.000Z",
  "message": "Approval applies, so this action was queued for a person to approve, edit or reject in the Postgale dashboard. Nothing was sent yet. …"
}
  • Postgale stores the exact call and its SHA-256 hash, and who proposed it (the API key, or the connected app and the person who connected it).
  • In Dashboard → Approvals a member approves it (the stored call runs right away, as your key, through the same code path as a direct request), edits the text (the payload is re-hashed and must be approved again) or rejects it, optionally with a reason.
  • Follow it with GET /actions/{id} (or GET /actions?status=pending_approval): status moves from pending_approval to executed (with result, the response the endpoint would have returned), failed (with error) or rejected (with review.rejectReason). text is what was or will be sent and edited is true when a person changed it.
  • The same events arrive as webhooks: action.pending_approval, action.approved, action.rejected, action.executed and action.failed.
  • Retrying the same call while it is still pending returns the same action with replayed: true instead of queueing it twice.
  • Every step is in the audit log: the proposal, the review, the operation itself (tagged with the action and the reviewer) and its outcome.

Reads are never queued. Ads writes are still refused with 403 approval_required.

Connecting accounts

Accounts are connected by their owners through OAuth: GET /connect/{platform} returns an OAuth URL for the account owner (valid 10 minutes); ?externalId= is stored on the new account(s). For WhatsApp it is the Embedded Signup page. Telegram has no OAuth: the answer is a one-time /connect code to post in the channel or group (see the Telegram guide). For Discord the owner picks the server and channel on Discord (see the Discord guide). Mastodon needs the server: ?instance=hachyderm.io (see the Mastodon guide). Bluesky has no OAuth link: POST /connect/bluesky with {handle, appPassword} connects the account right away with a Bluesky app password (see the Bluesky guide). When the owner has to pick between several options (e.g. Facebook Pages), the result is a pending connection: list it with GET /connect/pending, read its options with GET /connect/pending/{id} and connect them with POST /connect/pending/{id}/select (body {optionIds}, within 30 minutes). The full list is in Endpoints.

On this page