Apps (account API)
List, create, update, delete, and toggle your PrivacyFlow apps via the account-management API — the canonical way to display all apps on your account.
Apps are the core resource on a PrivacyFlow account. Each app is one connected messenger profile (one Signal number, one SimpleX profile, or one Session ID) plus a delivery config (poll / webhook / all). Every credential your clients use is scoped to one or more appIds. Listing, creating, and mutating apps is the most common account-management operation.
This page is API-first — every curl below is runnable as-is with a valid session cookie. The “Dashboard UI mapping” section at the end shows which UI feature each route backs.
Auth, in one paragraph
Every call in this page needs the session cookie (HTTP-only, set by the passwordless auth flow) and, for writes, an Origin header matching the dashboard host. Read Authentication for the full model. Quick reference:
SESSION='session=xyz123abc...'
ORIGIN='https://privacyflow.app'
List all apps
GET /dashboard/api/dashboard/apps
The canonical “display all apps” endpoint. Returns one item per app, each with full metadata.
curl -s "https://privacyflow.app/dashboard/api/dashboard/apps" \
-H "Cookie: $SESSION" \
-H "Accept: application/json"
Response:
{
"items": [
{
"id": "app_123",
"name": "Support — Signal",
"protocol": ["signal"],
"status": "active",
"deliveryType": "poll",
"webhookUrl": null,
"webhookAuthType": null,
"webhookAuthHeaderName": null,
"apiKeyId": "cred_abc",
"userId": "user_456",
"createdAt": "2026-04-12T08:30:00Z",
"updatedAt": "2026-05-30T14:12:00Z"
},
{
"id": "app_456",
"name": "Sales — SimpleX",
"protocol": ["simplex"],
"status": "active",
"deliveryType": "webhook",
"webhookUrl": "https://example.com/inbound",
"webhookAuthType": "bearer",
"webhookAuthHeaderName": null,
"apiKeyId": "cred_def",
"userId": "user_456",
"createdAt": "2026-04-15T10:00:00Z",
"updatedAt": "2026-06-01T09:30:00Z"
}
]
}
Per-app schema
| Field | Type | Notes |
|---|---|---|
id | string | The appId you pass to the public API on send. |
name | string | Operator-visible name. Not exposed to end users. |
protocol | string[] | Which messengers this app accepts. Subset of signal, simplex, session. |
status | "active" | "inactive" | active accepts messages; inactive does not. (Do not use enabled/disabled here — see dashboard conventions.) |
deliveryType | "poll" | "webhook" | "all" | Where PrivacyFlow sends inbound messages. poll queues only; webhook POSTs only; all does both. |
webhookUrl | string | null | HTTPS URL for webhook delivery. null when deliveryType === 'poll'. |
webhookAuthType | "bearer" | "header" | null | Set via the Webhook Auth Wizard. See webhook auth. |
webhookAuthHeaderName | string | null | For header-type webhook auth, the custom header name (typically X-Webhook-Token). null otherwise. |
apiKeyId | string | The credentialId of the credential used for webhook auth and any app-scoped ops. |
userId | string | The owner’s user ID (matches the /auth/verify session’s user). |
createdAt, updatedAt | ISO timestamp | Backend-managed. |
Create an app
POST /dashboard/api/dashboard/apps
Required: name (non-empty string) and protocol (array). All other fields optional — sensible defaults apply.
curl -s -X POST "https://privacyflow.app/dashboard/api/dashboard/apps" \
-H "Cookie: $SESSION" \
-H "Origin: $ORIGIN" \
-H "Content-Type: application/json" \
-d '{
"name": "Support — Signal",
"protocol": ["signal"],
"status": "active",
"deliveryType": "poll"
}'
Optional body fields:
| Field | Type | Notes |
|---|---|---|
status | "active" | "inactive" | default active. |
deliveryType | "poll" | "webhook" | "all" | default poll. |
webhookUrl | string | Required if deliveryType includes webhook. |
webhookAuthType | "bearer" | "header" | Required with webhookUrl. |
webhookAuthSecret | string | The pf_live_… key. Required with webhookUrl. |
webhookAuthHeaderName | string | Required if webhookAuthType === 'header' (typically X-Webhook-Token). |
apiKeyId | string | Credential id to attach. Optional — the backend will look it up or create one. |
Returns the created app object (same shape as a list item).
Update an app
POST /dashboard/api/dashboard/apps/update
Path-keyed (not REST-style PUT). Required: appId. Forwards the body unchanged.
curl -s -X POST "https://privacyflow.app/dashboard/api/dashboard/apps/update" \
-H "Cookie: $SESSION" \
-H "Origin: $ORIGIN" \
-H "Content-Type: application/json" \
-d '{
"appId": "app_123",
"deliveryType": "webhook",
"webhookUrl": "https://example.com/inbound",
"webhookAuthType": "bearer",
"webhookAuthSecret": "pf_live_…"
}'
This is the route the Webhook Auth Wizard calls when you save; it can also flip deliveryType from poll to webhook to all and back. name and protocol are create-only fields — for name/messenger changes, delete and recreate the app.
Delete an app
DELETE /dashboard/api/dashboard/apps/:appId
Must include Content-Type: application/json even with no body — the proxy returns 403 without it.
curl -s -X DELETE "https://privacyflow.app/dashboard/api/dashboard/apps/app_123" \
-H "Cookie: $SESSION" \
-H "Origin: $ORIGIN" \
-H "Content-Type: application/json"
Deletion is immediate. Any credentials scoped to app_123 become unusable for send (their appId is no longer in their key’s appIds[]). Polling a deleted appId returns empty queues forever (or 404 if the queue is pruned).
Toggle a messenger on an app
PUT /dashboard/api/dashboard/app-messengers/:appMessengerId
appMessengerId is the join-record id (one row per messenger per app), not the appId. You’ll see it if you expand an app’s messengers in the dashboard UI; you can’t derive it from the app object alone — list the app’s messengers first.
curl -s -X PUT "https://privacyflow.app/dashboard/api/dashboard/app-messengers/am_789" \
-H "Cookie: $SESSION" \
-H "Origin: $ORIGIN" \
-H "Content-Type: application/json" \
-d '{ "status": "disabled" }'
status accepts active or disabled. Not enabled. System-only processing cannot be set via the API; that state is set by the backend during provisioning.
Errors you’ll see
| Status | When |
|---|---|
400 {success:false, error:'Session token is required'} | Missing session cookie. |
400 'name is required' | POST /apps with no name. |
400 'protocol must be an array' | POST /apps with protocol missing or not an array. |
400 'appId is required' | POST /apps/update with no appId. |
400 'Cross-origin requests are not allowed' | POST/PUT/DELETE with a wrong or missing Origin header. |
403 Forbidden | DELETE without Content-Type: application/json. |
404 | Path not on the allowlist. |
504 Gateway Timeout | Backend took longer than 8 seconds. |
Tip
For most automation, you only need GET /apps (list) and POST /apps/update (flip delivery type). The create / delete endpoints are operator-only — most users add apps via the dashboard UI’s “Connect” button, which uses the same routes but with the user picking a messenger first.
Dashboard UI mapping
Each UI feature in the Apps section of the dashboard is a thin wrapper around one of the routes above. This map tells you which API call to make if you want to script what a button does.
| UI action | Underlying API route |
|---|---|
| Apps page loads, shows the grid | GET /dashboard/api/dashboard/apps |
| Click “New app” → fill form → save | POST /dashboard/api/dashboard/apps |
| Click app’s “Settings” → change delivery → save | POST /dashboard/api/dashboard/apps/update |
| Webhook Auth Wizard: choose method → save | POST /dashboard/api/dashboard/apps/update (with webhookUrl, webhookAuthType, webhookAuthSecret, webhookAuthHeaderName) |
| Click app’s “Delete” → confirm | DELETE /dashboard/api/dashboard/apps/:id |
| Toggle a messenger active/disabled on an app card | PUT /dashboard/api/dashboard/app-messengers/:id |
See Credentials for the route that issues the apiKeyId referenced by each app object, and Webhook auth for the wizard that writes the webhook fields.