PushHub REST API Reference

Base URLs:

The full machine-readable spec is available at docs/openapi.yaml.


Authentication

All endpoints (except health and auth) require one of:

Bearer JWT (dashboard users)

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR...

Obtain a JWT by calling POST /auth/login.

API Key (SDK / server-to-server)

Authorization: ApiKey ph_your_api_key_here

API keys are scoped to a single app. Create and revoke them in the dashboard under App Settings → API Keys.


Common Response Formats

Success

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "My App",
  "createdAt": "2024-01-15T12:00:00Z"
}

Validation Error (400)

{
  "message": "Validation failed",
  "errors": {
    "title": ["Title is required"],
    "body": ["Body must not exceed 1000 characters"]
  }
}

Error (4xx / 5xx)

{
  "message": "Resource not found",
  "code": "RESOURCE_NOT_FOUND"
}

Pagination

List endpoints return a paged response:

{
  "items": [...],
  "total": 1250,
  "page": 1,
  "pageSize": 50,
  "totalPages": 25
}

Pass ?page=2&pageSize=50 to paginate.


Endpoints

Health

GET /health

No authentication required. Returns service status.

Response 200:

{
  "status": "Healthy",
  "version": "1.0.0",
  "timestamp": "2024-01-15T12:00:00Z",
  "checks": {
    "database": "Healthy",
    "redis": "Healthy"
  }
}

Authentication

POST /auth/register

Create a new account.

Request:

{
  "email": "dev@example.com",
  "password": "SecurePass123!",
  "name": "Alex Johnson"
}

Response 201:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR...",
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2...",
  "expiresAt": "2024-01-16T12:00:00Z",
  "user": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "dev@example.com",
    "name": "Alex Johnson",
    "createdAt": "2024-01-15T12:00:00Z"
  }
}

POST /auth/login

Request:

{
  "email": "dev@example.com",
  "password": "SecurePass123!"
}

Response 200: Same as register response.


POST /auth/refresh

Request:

{ "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2..." }

Response 200: Same as register response.


GET /auth/me

Returns the current authenticated user.

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "email": "dev@example.com",
  "name": "Alex Johnson",
  "createdAt": "2024-01-15T12:00:00Z"
}

Apps

GET /apps

List all apps for the authenticated account.

Response 200:

[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "My Game App",
    "bundleId": "com.example.mygame",
    "platform": "Android",
    "hasFcmConfig": true,
    "hasApnsConfig": false,
    "createdAt": "2024-01-15T12:00:00Z",
    "updatedAt": "2024-01-15T12:00:00Z"
  }
]

POST /apps

Request:

{
  "name": "My Game App",
  "bundleId": "com.example.mygame",
  "platform": "Android"
}

Response 201: Returns the created App object.


GET /apps/{appId}

Returns a single App object.


PUT /apps/{appId}

Request:

{ "name": "My Game App v2" }

DELETE /apps/{appId}

Returns 204 No Content.


PUT /apps/{appId}/fcm-config

Request:

{
  "serviceAccountJson": "{\"type\":\"service_account\",\"project_id\":\"my-project\",...}"
}

PUT /apps/{appId}/apns-config

Request:

{
  "keyId": "ABCDE12345",
  "teamId": "XYZ9876543",
  "bundleId": "com.example.mygame",
  "p8KeyContent": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
  "useSandbox": false
}

Devices

POST /devices/register

Auth: API Key

Register or update a device token. Call on every app launch.

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "deviceToken": "fMe4G8kT5Q:APA91bH...",
  "platform": "Android",
  "userId": "user-123",
  "deviceMetadata": {
    "country": "US",
    "language": "en",
    "appVersion": "2.1.0"
  }
}

Response 200:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "deviceToken": "fMe4G8kT5Q:APA91bH...",
  "platform": "Android",
  "userId": "user-123",
  "deviceMetadata": { "country": "US", "language": "en" },
  "isActive": true,
  "lastSeenAt": "2024-01-15T12:00:00Z",
  "createdAt": "2024-01-10T08:00:00Z"
}

GET /devices?appId={appId}&page=1&pageSize=50

Returns paginated PagedDevices.

Optional filters: platform, userId.


GET /devices/{deviceId}

Returns a single Device.


PUT /devices/{deviceId}

Auth: API Key

Request:

{
  "userId": "new-user-456",
  "deviceMetadata": { "subscriptionTier": "premium" }
}

DELETE /devices/{deviceId}

Returns 204 No Content.


Segments

POST /segments

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Premium US Users",
  "description": "Users on premium tier in the United States",
  "filter": {
    "operator": "AND",
    "conditions": [
      { "field": "country", "operator": "equals", "value": "US" },
      { "field": "subscriptionTier", "operator": "equals", "value": "premium" }
    ]
  }
}

Response 201: Returns Segment object.


GET /segments?appId={appId}

Returns array of Segment objects.


GET /segments/{segmentId}/device-count

{ "count": 45231 }

Campaigns

POST /campaigns

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Summer Sale Announcement",
  "title": "Summer Sale is LIVE!",
  "body": "Up to 50% off all in-game items this weekend only.",
  "imageUrl": "https://cdn.example.com/summer-sale.png",
  "deepLink": "myapp://store/sale",
  "customData": { "campaign_type": "promotional" },
  "targetType": "Segment",
  "segmentId": "seg-uuid-here"
}

Response 201: Returns Campaign object with status: "Draft".


POST /campaigns/{campaignId}/send

Send immediately.

Response 202:

{
  "campaignId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "Queued",
  "estimatedRecipients": 45231
}

POST /campaigns/{campaignId}/schedule

Request:

{ "scheduledAt": "2024-01-20T09:00:00Z" }

GET /campaigns/{campaignId}/analytics

Response 200:

{
  "campaignId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "sent": 45231,
  "delivered": 43891,
  "opened": 8234,
  "failed": 1340,
  "deliveryRate": 0.9703,
  "openRate": 0.1876,
  "platformBreakdown": {
    "android": { "sent": 28000, "delivered": 27300, "opened": 5100, "failed": 700 },
    "ios": { "sent": 17231, "delivered": 16591, "opened": 3134, "failed": 640 }
  }
}

Notifications

POST /notifications/send

Send a direct notification (up to 1000 devices per call).

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "deviceIds": ["device-uuid-1", "device-uuid-2"],
  "title": "Your order shipped!",
  "body": "Order #ORD-456 is on its way.",
  "deepLink": "myapp://orders/ORD-456",
  "customData": { "orderId": "ORD-456" }
}

Response 202:

{
  "notificationIds": ["notif-uuid-1", "notif-uuid-2"],
  "queued": 2,
  "rejected": 0
}

POST /notifications/track-delivery

Auth: API Key

{ "notificationId": "notif-uuid-1" }

Response 204


POST /notifications/track-opened

Auth: API Key

{ "notificationId": "notif-uuid-1" }

Response 204


Analytics

GET /analytics/overview?appId={appId}&from=2024-01-01&to=2024-01-31

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "period": { "from": "2024-01-01", "to": "2024-01-31" },
  "totalDevices": 125000,
  "activeDevices": 98000,
  "totalNotificationsSent": 2450000,
  "deliveryRate": 0.971,
  "openRate": 0.183,
  "platformBreakdown": { "android": 82000, "ios": 43000 }
}

GET /analytics/time-series?appId={appId}&from=2024-01-01&to=2024-01-07&granularity=day

{
  "granularity": "day",
  "data": [
    {
      "timestamp": "2024-01-01T00:00:00Z",
      "sent": 12500,
      "delivered": 12100,
      "opened": 2200,
      "failed": 400
    }
  ]
}

API Keys

POST /api-keys

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Production SDK Key"
}

Response 201:

{
  "id": "key-uuid",
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Production SDK Key",
  "prefix": "ph_abc123",
  "key": "ph_abc123def456ghi789jkl012mno345pqr678stu901",
  "createdAt": "2024-01-15T12:00:00Z",
  "lastUsedAt": null
}

The key field is returned only at creation. Store it immediately.


DELETE /api-keys/{keyId}

Returns 204 No Content. The key is immediately revoked.


Event Triggers

POST /event-triggers

Request:

{
  "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Post-Purchase Thank You",
  "eventName": "purchase_completed",
  "delayMinutes": 5,
  "conditions": [
    { "field": "amount", "operator": "greater_than", "value": 20 }
  ],
  "title": "Thanks for your purchase!",
  "body": "Your order {{properties.orderId}} is confirmed."
}

Response 201: Returns EventTrigger object.


POST /event-triggers/fire

Auth: API Key

Fire a custom event from the SDK.

Request:

{
  "deviceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "eventName": "purchase_completed",
  "properties": {
    "orderId": "ORD-789",
    "amount": 49.99,
    "currency": "USD"
  }
}

Response 202:

{ "triggersMatched": 1 }

Rate Limits

Endpoint group Limit
POST /auth/* 20 req/min per IP
POST /devices/register 1000 req/min per API key
POST /event-triggers/fire 5000 req/min per API key
POST /notifications/send 100 req/min per API key
All other endpoints 300 req/min per token

Rate limit headers are included in every response:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1705312800

SDK & Integration Guides