ApexDock's license model is offline-first and cryptographically signed. The server issues an Ed25519-signed grant per device; the app verifies the signature locally and keeps working through a 14-day offline grace window.
What you get with a license
- Activation on 3 / 10 / 25 Macs depending on the tier you bought (Solo / Team / Business) — no support tickets to add a Mac.
- A signed grant per machine that survives reboots, network outages, and 14 days offline.
- Self-serve activation, refresh, and deactivation — no email exchanges.
Single purchase, no subscription. Solo $39, Team $99 (10 seats), Business $199 (25 seats). Renewals run half price and are optional.
The flow
- Buy at apexdock.app/pricing → Stripe Checkout.
- The backend creates a license record under your ApexDock account.
- Each device activation trades your account credentials for a signed grant.
- The app verifies the grant locally on every launch.
- After 14 offline days, the app prompts to re-verify; before that, it just works.
Activating a device
Settings → License → Paste License Key (the email you received). Or sign in with your account credentials and the key is fetched automatically.
The activation request is a single POST to the licensing service with:
- The license key
- A device fingerprint (a stable SHA-256 of
IOPlatformUUID+ your username) - The device's display name (for the activation list)
The response is a JWT-style blob signed with the licensing service's Ed25519 private key. The app verifies the signature using the bundled public key, stores the grant in macOS Keychain, and unlocks the paid features.
Deactivating a device
Settings → License → Activated devices lists every slot on your tier. Click the ✕ next to any device to deactivate it remotely. The slot is freed immediately for a new device, and the deactivated Mac locks paid features within seconds (over Supabase Realtime). A deactivated Mac cannot silently re-activate; the user has to paste the license key again from Settings → License.
Refresh
If you've deactivated an old device but the new one says "no slots available", click Refresh in Settings → License. The app re-fetches grants and slot counts from the server.
Offline grace
Without network access, the app uses the cached grant indefinitely as long as it's been less than 14 days since last successful verification.
After 14 days offline, the app shows a banner asking to re-verify. Features keep working until you dismiss the banner; dismissing it locks paid features until verification succeeds.
The 14-day window resets every time you go online and the app silently re-verifies (typically once a day on launch).
Verification details
- The signed grant is Ed25519, base64-url-encoded, with a JSON payload containing
userId,deviceFingerprint,expiresAt,slotIndex. - The bundled public key is hardcoded in the app binary; the matching private key lives only on the licensing server.
- The app refuses grants where the signature fails, where
userIddoesn't match the stored account, wheredeviceFingerprintdoesn't match this machine, or whereexpiresAtis past. - A grant lasts 90 days. Online verification renews it transparently; nothing user-facing changes.
Multiple users on the same Mac
Each macOS user account has its own activation slot. A Mac shared between two users counts as 2 of your 3 devices.
Migrating devices
When you replace a Mac:
- On the new Mac: Settings → License → Paste License Key.
- The activation tries to consume a free slot. If all 3 are in use, you get an "all slots used" message.
- Open Settings → License on any other Mac (or apexdock.app/account in a browser) and ✕ the slot you want to free.
- Back on the new Mac, click Refresh → activation succeeds.
Trials
apexdock.app/pricing offers a 7-day trial that captures a card via Stripe Checkout in mode=setup (no charge up front). On day 7 a Postgres cron job runs an off-session PaymentIntent; success converts the trial to an active license, failure revokes it.
Trial accounts get the same builds as paid licenses. The Sparkle appcast and the /api/v1/download endpoint both treat status = trialing as fully entitled, so a trial Mac can install ApexDock and pull updates the moment the license is created.
/account shows a TrialCard while the trial is open with two actions:
- Cancel trial —
POST /api/trials/canceldetaches the saved payment method and revokes the license. The desktop locks within ~1s via Supabase Realtime. - Update payment method —
POST /api/trials/portalopens a Stripe Billing Portal session restricted to PM updates.
The cron-only conversion endpoint (POST /api/trials/charge, Bearer-authenticated against TRIAL_CRON_SECRET) is internal and not part of the user-facing API.
API surface
Licensing is GUI-driven from the desktop app; activation/deactivation/refresh and trial management all happen through the Settings → License panel and the /account page. A REST API for MDM-style device provisioning is on the roadmap.
Privacy
Licensing requests carry only:
- The license key (proves ownership)
- The device fingerprint (binds a slot)
- The device's display name (for your activation list)
No telemetry, no usage data, no IP-based geolocation. The server logs activation attempts for fraud detection but doesn't track ongoing usage.