Plush

Plush API

Send customizable, encrypted push notifications to every linked Apple device or one device by name.

Base URLhttps://api.easypush.app
export PLUSH_API_TOKEN="push_..."

curl https://api.easypush.app/v1/devices \
  -H "Authorization: Bearer $PLUSH_API_TOKEN"

Auth

The iOS app signs in with Apple and receives a family API token from the server. API calls use bearer auth.

POST /v1/auth/apple App-only exchange for a Sign in with Apple identity token.
{
  "identityToken": "eyJhbGciOi..."
}

Devices

Devices register their APNs token and an encryption public key. Device names must be camelCase, kebab-case, or snake_case.

GET /v1/devices List enabled devices linked to the Apple account.
POST /v1/devices Register or refresh the current device.
{
  "installationId": "device-local-uuid",
  "name": "workPhone",
  "platform": "iOS",
  "pushToken": "apns-token",
  "publicKey": "base64-public-key",
  "publicKeyAlgorithm": "x25519-hkdf-sha256-aes-gcm",
  "appVersion": "1.0",
  "osVersion": "iOS 18"
}
PATCH /v1/devices/:id Rename or disable a device.
DELETE /v1/devices/:id Remove a device.

Pushes

Every push includes APNs-visible copy and encrypted payload envelopes keyed by target device ID or device name.

POST /v1/pushes Send to all devices, named devices, or explicit device IDs.
{
  "target": { "kind": "all" },
  "visibleAlert": {
    "title": "Build passed",
    "subtitle": "Plush",
    "body": "main deployed successfully",
    "sound": "default",
    "threadId": "deploys",
    "interruptionLevel": "active",
    "relevanceScore": 0.8
  },
  "encryptedPayloads": {
    "workPhone": {
      "algorithm": "x25519-hkdf-sha256-aes-gcm",
      "ciphertext": "...",
      "nonce": "...",
      "tag": "...",
      "ephemeralPublicKey": "...",
      "sentAt": "2026-06-12T00:00:00Z"
    }
  }
}
Target kindShape
all{ "kind": "all" }
deviceNames{ "kind": "deviceNames", "names": ["workPhone"] }
deviceIds{ "kind": "deviceIds", "ids": ["..."] }

Self-host

The server is a small Cloudflare Worker with D1. Custom deployments can set BILLING_MODE=custom to skip hosted subscription checks.

cd server
npm install
npx wrangler d1 create plush-api
npm run db:migrate:remote
npx wrangler secret put APNS_PRIVATE_KEY
npx wrangler deploy

Errors

Errors return JSON with a message and optional details.

StatusMeaning
401Missing or invalid bearer token.
402Hosted API access needs an active subscription.
422Missing encrypted payloads for a target device.
503APNs credentials are not configured.