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

FieldTypeNotes
idstringThe appId you pass to the public API on send.
namestringOperator-visible name. Not exposed to end users.
protocolstring[]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.
webhookUrlstring | nullHTTPS URL for webhook delivery. null when deliveryType === 'poll'.
webhookAuthType"bearer" | "header" | nullSet via the Webhook Auth Wizard. See webhook auth.
webhookAuthHeaderNamestring | nullFor header-type webhook auth, the custom header name (typically X-Webhook-Token). null otherwise.
apiKeyIdstringThe credentialId of the credential used for webhook auth and any app-scoped ops.
userIdstringThe owner’s user ID (matches the /auth/verify session’s user).
createdAt, updatedAtISO timestampBackend-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:

FieldTypeNotes
status"active" | "inactive"default active.
deliveryType"poll" | "webhook" | "all"default poll.
webhookUrlstringRequired if deliveryType includes webhook.
webhookAuthType"bearer" | "header"Required with webhookUrl.
webhookAuthSecretstringThe pf_live_… key. Required with webhookUrl.
webhookAuthHeaderNamestringRequired if webhookAuthType === 'header' (typically X-Webhook-Token).
apiKeyIdstringCredential 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

StatusWhen
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 ForbiddenDELETE without Content-Type: application/json.
404Path not on the allowlist.
504 Gateway TimeoutBackend 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 actionUnderlying API route
Apps page loads, shows the gridGET /dashboard/api/dashboard/apps
Click “New app” → fill form → savePOST /dashboard/api/dashboard/apps
Click app’s “Settings” → change delivery → savePOST /dashboard/api/dashboard/apps/update
Webhook Auth Wizard: choose method → savePOST /dashboard/api/dashboard/apps/update (with webhookUrl, webhookAuthType, webhookAuthSecret, webhookAuthHeaderName)
Click app’s “Delete” → confirmDELETE /dashboard/api/dashboard/apps/:id
Toggle a messenger active/disabled on an app cardPUT /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.

Last updated: