# Authentication

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

GET /api/v1/accounts:
```bash
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:
```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** 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](https://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 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://postgale.com/docs/telegram)). For Discord the owner
picks the server and channel on Discord (see the [Discord guide](https://postgale.com/docs/discord)). Mastodon needs the server: `?instance=hachyderm.io`
(see the [Mastodon guide](https://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://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://postgale.com/docs/api#accounts--connect).
