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/dashboardAstro base prefix, the/api/dashboard/proxy prefix, then the resource path. - Auth. The HTTP-only
sessioncookie, set during passwordless login. A missing or expired cookie returns400 { success: false, error: "Session token is required" }. See Authentication. - Content-Type.
application/jsonon every request with a body.DELETErequires the header even without a body — omitting it returns403. - Origin enforcement.
POST/PUT/DELETEmust be same-origin. MissingOriginis permitted for non-browser clients; cross-origin returns400 { 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 bothresponse.okand a known result field. - Timeouts. 8 seconds. Timeout returns
504; unreachable backend returns502; non-2xx backend responses forward the JSON error body when parseable, otherwise returnBackend 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.
List members / get invite link
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 (pending → confirming → paid).
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
| Code | When |
|---|---|
200 | GET/POST success. |
400 | Missing session token, missing required body field, invalid JSON, cross-origin mutation, unknown sub-resource. |
403 | Wrong Content-Type on a DELETE/PUT. |
404 | Path not on the allowlist. |
500 | Uncaught internal error. |
502 | Backend unreachable (network failure). |
504 | Backend 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 athttps://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.