Account API reference

Every route on the PrivacyFlow account-management API — method, path, body, response shape, and the session-cookie auth model.

The PrivacyFlow account-management API lives at /dashboard/api/dashboard/* and exposes the full dashboard surface: list apps, manage credentials, configure billing, run groups. Authentication is via the dashboard session cookie, not an API key. The proxy injects the session as X-Session-Token, enforces an allowlist of routes, applies an 8-second timeout, and forwards sanitized errors.

Conventions

  • Base URL. https://privacyflow.app/dashboard/api/dashboard/* — the /dashboard Astro base prefix, the /api/dashboard/ proxy prefix, then the resource path.
  • Auth. The HTTP-only session cookie, set during passwordless login. A missing or expired cookie returns 400 { success: false, error: "Session token is required" }. See Authentication.
  • Content-Type. application/json on every request with a body. DELETE requires the header even without a body — omitting it returns 403.
  • Origin enforcement. POST/PUT/DELETE must be same-origin. Missing Origin is permitted for non-browser clients; cross-origin returns 400 { error: "Cross-origin requests are not allowed" }.
  • Response envelope. Response shapes vary by route — some return the backend object directly ({ id, name, ... }), some return { success: true, data: {...} }, some return { success: false, error }. Check both response.ok and a known result field.
  • Timeouts. 8 seconds. Timeout returns 504; unreachable backend returns 502; non-2xx backend responses forward the JSON error body when parseable, otherwise return Backend request failed (status).

Note

These routes require a logged-in dashboard session. To script account-management operations, obtain the session cookie from a real login (via the passwordless auth flow) and reuse it in your client. Re-authenticate on 401 — cookies expire.

User

Get current user

GET /dashboard/api/dashboard/user

Returns the logged-in user’s profile.

{ "id": "user_456", "name": "Jane Doe", "email": "jane@example.com" }

Update user name

POST /dashboard/api/dashboard/user

{ "name": "Jane Doe" }

400 if name is missing.

Apps

The full apps-management surface. GET /dashboard/api/dashboard/apps is the canonical “display all apps” endpoint — use it to list every app on your account with full metadata.

List all apps

GET /dashboard/api/dashboard/apps

Returns an object keyed under items, one entry per app. Query parameters are forwarded to the backend.

{
  "items": [
    {
      "id": "app_123",
      "name": "Support — Signal",
      "protocol": ["signal"],
      "status": "active",
      "deliveryType": "poll",
      "webhookUrl": null,
      "webhookAuthType": null,
      "apiKeyId": "cred_abc",
      "userId": "user_456",
      "createdAt": "2026-04-12T08:30:00Z"
    }
  ]
}

See the apps page for the full per-app schema, creation, mutation, and toggle flows.

Create an app

POST /dashboard/api/dashboard/apps

Body (every field optional unless noted):

{
  "name": "Support — Signal",
  "protocol": ["signal", "simplex"],
  "status": "active",
  "deliveryType": "poll",
  "webhookUrl": "https://example.com/wh",
  "webhookAuthType": "bearer",
  "webhookAuthSecret": "pf_live_…",
  "webhookAuthHeaderName": null,
  "apiKeyId": "cred_abc"
}

Required: name (string, non-empty) and protocol (array). All other fields optional. Returns the created app object.

Update an app

POST /dashboard/api/dashboard/apps/update

{
  "appId": "app_123",
  "...fields to mutate..."
}

Required: appId. Forwards the body unchanged. Used by the Webhook Auth Wizard to set webhookUrl, webhookAuthType, webhookAuthSecret, and by the app settings modal to set deliveryType.

Delete an app

DELETE /dashboard/api/dashboard/apps/:appId

No body. Content-Type: application/json header required. Returns the delete result.

Toggle a messenger on an app

PUT /dashboard/api/dashboard/app-messengers/:id

id is the app-messenger join record id (not the appId). Body:

{ "status": "active" }

status must be active or disabled (not enabled — see dashboard conventions). System-only processing cannot be set.

Credentials

List credentials (metadata only)

GET /dashboard/api/dashboard/credentials

Returns credential objects with secret fields redacted. Server-rendered into the dashboard page; not on the public catch-all.

List credentials (full secret)

GET /dashboard/api/dashboard/credentials-full

Returns the same list with the secret strings exposed — use this when you need the actual pf_live_... value (e.g., re-pasting into a client after losing your config). Treat the response as sensitive.

Create a credential

POST /dashboard/api/dashboard/credentials

Body shape depends on the credential type. Common fields: type (api-key | webhook-bearer | webhook-header), appIds (array), name. For webhook types, also webhookUrl, webhookAuthType, etc. Returns the created credential including a one-time-viewable secret.

Delete a credential

DELETE /dashboard/api/dashboard/credentials/:id

No body. Content-Type: application/json header required. No grace period — the key string starts returning 403 (poll/send) or 401 (verify) immediately. See credentials.

Recovery code

POST /dashboard/api/dashboard/recovery-code

Empty body. Triggers a recovery-code flow on the backend. Used after credential loss to regain access. Returns the new recovery code once.

Notification preferences

Read

GET /dashboard/api/dashboard/notification-preferences

Returns the per-messenger and per-category alert toggles.

Write

POST /dashboard/api/dashboard/notification-preferences

Body: the same shape the GET returned, mutated. Toggles are applied immediately.

Groups

All group endpoints take an appMessengerId (either as query for GET, or in the body for POST). See groups.

GET /dashboard/api/dashboard/group/members?appMessengerId=<id>

GET /dashboard/api/dashboard/group/invite-link?appMessengerId=<id>

Member operations

POST /dashboard/api/dashboard/group/members/add → body: { appMessengerId, contactId }

POST /dashboard/api/dashboard/group/members/remove → body: { appMessengerId, contactId }

Admin operations

POST /dashboard/api/dashboard/group/promote-admin → body: { appMessengerId, contactId }

POST /dashboard/api/dashboard/group/demote-admin → body: { appMessengerId, contactId }

Invoices

List invoices

GET /dashboard/api/dashboard/invoices

Get a single invoice

GET /dashboard/api/dashboard/invoices/:invoiceId

Get payment status

GET /dashboard/api/dashboard/invoices/:invoiceId/payment-status

Used by the CryptoPaymentModal to poll for status transitions (pendingconfirmingpaid).

Create an invoice

POST /dashboard/api/dashboard/invoices → body: { asset, planId } (depending on the active billing configuration).

Cancel an invoice

POST /dashboard/api/dashboard/invoices/:invoiceId/cancel → empty body or {}. Moves the invoice to cancelled.

Configuration

GET /dashboard/api/dashboard/configuration

Returns the public billing configuration — supported crypto assets, their networks, plan prices (free + paid), annual discount percent, currency. Used by both server-rendered pages and client-side components to populate the Billing UI without hardcoding prices. This route is publicly readable; no session cookie is required.

Status code map

CodeWhen
200GET/POST success.
400Missing session token, missing required body field, invalid JSON, cross-origin mutation, unknown sub-resource.
403Wrong Content-Type on a DELETE/PUT.
404Path not on the allowlist.
500Uncaught internal error.
502Backend unreachable (network failure).
504Backend exceeded the 8-second timeout.

Surface boundaries

The account-management API is allowlist-only. The following are not proxied and return 404:

  • Any path not in the route catalog above (e.g., POST /dashboard/api/dashboard/unknown-thing).
  • The public messaging API (/api/v1/messages/send, /poll, etc.) — those live at https://privacyflow.app/api/v1/*, not under /dashboard/api/*. The dashboard does not proxy to the public API.
  • Server-rendered dashboard paths (e.g., /dashboard/stats, /dashboard/realtime-stats, /dashboard/main, /dashboard/apps/:id/metrics) — these are read by the dashboard pages directly during server-side rendering, not exposed via the catch-all proxy.
Last updated: