# Authentication

Source: https://staging.postgale.com/docs/api/authentication

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

## API keys

- Base URL: `https://staging.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](https://staging.postgale.com/docs/mcp), with the same permissions, rate limit and approval rules.

GET /api/v1/accounts:
```bash
curl https://staging.postgale.com/api/v1/accounts \
  -H 'Authorization: Bearer sk_…'
```

## Approval

Approval is on by default for new organizations, and can also be required per account, API key or connected app. When the
organization, an account or the key requires approval, new posts are held as `pending_approval` until an owner or admin 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, pin, like, comment settings), mention and review replies, WhatsApp broadcasts, flow messages and calls,
Discord and Telegram writes, account settings changes, creating a webhook, 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:
```json
{
  "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** an owner or admin 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](https://staging.postgale.com/docs/api/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 and WhatsApp business-settings 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](https://staging.postgale.com/docs/telegram)). For Discord the owner
picks the server and channel on Discord (see the [Discord guide](https://staging.postgale.com/docs/discord)). Mastodon needs the server: `?instance=hachyderm.io`
(see the [Mastodon guide](https://staging.postgale.com/docs/mastodon)). 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](https://staging.postgale.com/docs/bluesky)). 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](https://staging.postgale.com/docs/api#accounts--connect).
