Pulli Developer Portal
REST API reference for the Pulli macOS download manager
Pulli exposes a REST API for release information, app updates, download distribution, public statistics, license management, and billing. The API is hosted at https://pulli.app. There is no separate user login — the free license key is the account.
Authentication
Pulli uses three authentication tiers depending on the endpoint:
- None
- Public endpoints (health, releases, stats, downloads) require no authentication. Rate limited by IP.
- License key in body
- Account management endpoints require a
licenseKeyfield in the JSON request body. The license key format ispulli-XXXX-XXXX-XXXX. - App auth
- Device-scoped endpoints (validate, deactivate) require an
x-machine-uidheader and a signed request body. The signing token (requestSignToken) is returned during license activation. The signature is Ed25519-based and verified server-side on every request.
Release channels
Pulli publishes two release channels:
- stable
- Production releases. The default channel for all endpoints. Available via
/api/downloadand/api/app/update. - nightly
- Pre-release builds for testing. Pass
?channel=nightlyto release and update endpoints.
API reference
All endpoints return JSON unless noted otherwise (download endpoints stream binary data). The full machine-readable spec is available at /openapi.json.
Public
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /api/health | Service health check (database, KV, environment) | None |
| GET | /api/stats | Public install/download statistics, cached 1 hour | None |
| POST | /api/event | Track a page_view or download event | None |
Releases
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /api/releases | List releases, optionally filtered by ?channel=stable|nightly | None |
| GET | /api/get-latest-version | Get the latest released version number | None |
| GET | /api/download?arch=x86_64|aarch64 | Download the latest DMG installer (supports range requests) | None |
| GET | /api/check-update?arch&version&channel | Check for app updates; 204 if up-to-date, 200 with signature+URL if update available | None |
| GET | /api/app/update?arch&channel | Download the update tarball (.tar.gz) for the auto-updater | None |
Accounts
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /api/v1/accounts/generate | Generate a free license key (pulli-XXXX-XXXX-XXXX) | None |
| POST | /api/v1/accounts/recover | Recover license key by email | None |
| POST | /api/v1/accounts/status | Check license type, status, devices, and email | License key in body |
| POST | /api/v1/accounts/email | Attach or update the email on a license | License key in body |
| POST | /api/v1/accounts/delete | Permanently delete account and all associated data | License key in body |
Licenses
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /api/v1/licenses/activate | Activate a license on a device; returns a requestSignToken for authenticated calls | None |
| POST | /api/v1/licenses/validate | Validate the authenticated device's license | App auth (x-machine-uid + signed body) |
| POST | /api/v1/licenses/deactivate | Remove the device activation for the authenticated machine | App auth (x-machine-uid + signed body) |
Billing
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /api/v1/billing/checkout | Create a Polar.sh checkout URL for upgrading to Pro | License key in body |
Rate limits
All endpoints are rate limited. Public endpoints are limited by IP. Account and license endpoints have per-hour buckets. If you hit a rate limit, the API returns 429 Too Many Requests with a Retry-After header.
Example requests
Check for updates
GET /api/check-update?arch=aarch64&version=1.1.0&channel=stable
# 200 — update available
{ "version": "1.2.0", "signature": "...", "url": "..." }
# 204 — up to dateGenerate a free license
POST /api/v1/accounts/generate
{ "success": true, "data": { "licenseKey": "pulli-XXXX-XXXX-XXXX" } }Activate a license
POST /api/v1/licenses/activate
Content-Type: application/json
{ "licenseKey": "pulli-XXXX-XXXX-XXXX", "machineUid": "abc123", "platform": "darwin", "appVersion": "1.2.0" }
{ "success": true, "data": { "key": "pulli-XXXX-XXXX-XXXX", "licenseType": "free", "requestSignToken": "..." } }List releases
GET /api/releases?channel=stable
[{ "version": "1.2.0", "channel": "stable", "notes": "...", "summary": "...", "createdAt": "2025-07-01T..." }]Resources
- OpenAPI Specification — machine-readable API spec (JSON)
- llms.txt — AI agent discovery file
- Developer Console — manage licenses and releases
- Pricing — free and Pro plans
- Support — get help
- Discord Community — talk to us and other developers