# SmartAppPush — Server Integration Guide (OneSignal)

> **For AI coding tools.** Drop this file into your backend project and reference it when implementing SmartAppPush push notifications from your server. This guide is for apps where the mobile client uses OneSignal as the push delivery provider.
>
> Official docs: https://smartapppush.ai/docs

## What is the Developer Push API?

SmartAppPush's Developer API lets you send push notifications directly from your server with a single HTTP call. No dashboard, no campaigns needed. Use it for transactional and real-time notifications triggered by your backend logic.

**Use cases:**
- Order status updates — "Your order has shipped!"
- Real-time alerts — "New message from Sarah"
- Payment confirmations — "Payment of $49.99 received"
- Backend-triggered reminders — "Your reservation is in 1 hour"

> For bulk or recurring sends (e.g., re-engagement, promotions), use SmartAppPush Campaigns via the dashboard instead of the Developer API.

**How delivery works with OneSignal:** When you send a push via this API, SmartAppPush delivers it through OneSignal's API. The OneSignal SDK on the mobile app handles notification display automatically — the mobile app does not need to build notifications manually. Your `extra_data` is delivered via OneSignal's additional data mechanism and is accessible through the SDK's notification callbacks.

---

## Configuration

You need this value from the SmartAppPush dashboard:

| Key | Where to find | Usage |
|-----|--------------|-------|
| `server_key` | Dashboard → App → Settings | Authenticates server-to-server API calls |

**Base URL:** `https://api.smartapppush.ai`

> **`server_key` is a secret.** Do not expose it in client-side code, mobile apps, or public repositories. Store it in environment variables or a secrets manager.

---

## Send Push to User

### Endpoint

```
POST https://api.smartapppush.ai/developer/push
Content-Type: application/json
X-Server-Key: <your_server_key>
```

Authentication: `X-Server-Key` header with your app's server key.

### Request Example

```json
{
  "app_user_id": "user_123",
  "title": "Your order has shipped!",
  "body": "Order #4521 is on its way. Estimated delivery: March 31.",
  "extra_data": {
    "order_id": "4521",
    "screen": "order_detail",
    "tracking_url": "https://track.example.com/4521"
  },
  "schedule_at": "2026-03-29T15:00:00Z",
  "priority": 8,
  "dedupe_key": "order-shipped-4521"
}
```

### Minimal Request (immediate send)

```json
{
  "app_user_id": "user_123",
  "title": "Hello",
  "body": "World"
}
```

### cURL Examples

**Full request with scheduling:**

```bash
curl -X POST https://api.smartapppush.ai/developer/push \
  -H "Content-Type: application/json" \
  -H "X-Server-Key: <server_key>" \
  -d '{
    "app_user_id": "user_123",
    "title": "Your order shipped!",
    "body": "Track your package now.",
    "extra_data": {"order_id": "456", "deep_link": "/orders/456"},
    "schedule_at": "2026-03-28T15:00:00Z",
    "priority": 7,
    "dedupe_key": "order-shipped-456"
  }'
```

**Minimal request (immediate send):**

```bash
curl -X POST https://api.smartapppush.ai/developer/push \
  -H "Content-Type: application/json" \
  -H "X-Server-Key: <server_key>" \
  -d '{
    "app_user_id": "user_123",
    "title": "Hello",
    "body": "World"
  }'
```

### Response

```json
// 201 Created
{
  "push_count": 2,
  "app_user_id": "user_123",
  "app_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
```

- `push_count` — Number of devices the push was sent to (one user can have multiple devices)
- `app_user_id` — The user the push was sent to
- `app_id` — Your app's UUID

### Field Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `app_user_id` | string | **Yes** | The user to send the push to. Must match exactly what the mobile app sends in events |
| `title` | string | **Yes** | Push notification title |
| `body` | string | **Yes** | Push notification body text |
| `extra_data` | object | No | Custom key-value data delivered in the push payload. The mobile app receives this when the user taps the notification — use for deep linking, screen navigation, or passing context |
| `schedule_at` | string | No | RFC 3339 timestamp for scheduled delivery (e.g., `2026-03-29T15:00:00Z`). Sends immediately if omitted |
| `priority` | integer | No | 1 (lowest) to 10 (highest). Default: 5 |
| `dedupe_key` | string | No | Prevents duplicate pushes. Same key within a 10-minute window → second push is silently skipped. Auto-generated if omitted |

### Error Responses

| Status | Body | Meaning |
|--------|------|---------|
| `400` | `{"errors": ["app_user_id is required", "title is required"]}` | Missing required fields or invalid format |
| `401` | `{"error": "missing X-Server-Key header"}` | No `X-Server-Key` header provided |
| `401` | `{"error": "invalid server key"}` | The server key doesn't match any app |
| `404` | `{"error": "no devices with push permission found for this user"}` | User has no devices with push enabled |
| `429` | — | Rate limit exceeded (100 req/sec per IP). Check `Retry-After` header |
| `503` | — | Server overloaded. Retry after a short delay |

---

## How Pushes Are Delivered to the Mobile App

When you send a push via this API, SmartAppPush delivers it through OneSignal's API. Here's what happens on the mobile side:

1. SmartAppPush constructs a OneSignal notification with `title`, `body`, and your `extra_data` as additional data, plus a `tracking_token` for open tracking
2. OneSignal delivers the notification to the device
3. The **OneSignal SDK displays the notification automatically** — the mobile app does not need to build it
4. When the user taps the notification, the OneSignal SDK's click listener provides the additional data
5. The mobile app extracts `tracking_token` and `extra_data` fields from the notification's additional data

### How extra_data Arrives on Mobile

```kotlin
// Android — OneSignal click handler
OneSignal.Notifications.addClickListener { event ->
    val additionalData = event.notification.additionalData
    val trackingToken = additionalData?.optString("tracking_token")
    val screen = additionalData?.optString("screen")
    val orderId = additionalData?.optString("order_id")
    // ...
}
```

```swift
// iOS — OneSignal click handler
OneSignal.Notifications.addClickListener { event in
    let additionalData = event.notification.additionalData
    let trackingToken = additionalData?["tracking_token"] as? String
    let screen = additionalData?["screen"] as? String
    // ...
}
```

Key points for your `extra_data` design:
- `extra_data` is delivered as-is via OneSignal's additional data — the structure is preserved
- SmartAppPush automatically injects a `tracking_token` UUID for open tracking — you do not need to include it
- Use consistent key names (e.g., `screen`, `order_id`) so the mobile app's deep link router can handle them predictably

---

## Use Case Examples

### Transactional: Order Shipped

```json
{
  "app_user_id": "user_456",
  "title": "Your order has shipped!",
  "body": "Order #7890 is on its way.",
  "extra_data": {
    "screen": "order_tracking",
    "order_id": "7890"
  },
  "dedupe_key": "order-shipped-7890"
}
```

### Real-time: Chat Message

```json
{
  "app_user_id": "user_789",
  "title": "New message from Sarah",
  "body": "Hey, are you free for lunch tomorrow?",
  "extra_data": {
    "screen": "chat",
    "chat_id": "conv_123"
  },
  "priority": 9
}
```

### Scheduled: Appointment Reminder

```json
{
  "app_user_id": "user_321",
  "title": "Appointment tomorrow",
  "body": "Your appointment with Dr. Smith is at 10:00 AM.",
  "extra_data": {
    "screen": "appointments",
    "appointment_id": "apt_555"
  },
  "schedule_at": "2026-03-30T09:00:00Z",
  "dedupe_key": "reminder-apt-555"
}
```

---

## Do / Don't Rules

### Do

- **Store `server_key` in environment variables** or a secrets manager. Do not hardcode it.
- **Use `dedupe_key`** for idempotency. If your server retries a request (e.g., after timeout), the same `dedupe_key` within 10 minutes prevents duplicate pushes.
- **Handle 404 gracefully.** It means the user has no devices with push permission enabled — this is normal, not an error in your code.
- **Respect rate limits.** 100 requests/second per IP. If you get `429`, check the `Retry-After` header and wait.
- **Retry on `503`.** The server is temporarily overloaded. Wait a short delay and retry.
- **Use `extra_data` for deep linking.** Include screen names, entity IDs, or URLs so the mobile app can navigate the user to the right place when they tap the notification.
- **Match `app_user_id` exactly.** The value must be identical to what the mobile app sends in events. If the app sends `"usr_42"`, use `"usr_42"` here — not `"user_42"` or `42`.

### Don't

- **Do not expose `server_key` in client-side code**, mobile apps, frontend bundles, or public repositories.
- **Don't use the Developer API for bulk sends.** For campaigns targeting thousands of users, use SmartAppPush Campaigns via the dashboard. The Developer API is for targeted, individual pushes.
- **Don't ignore `push_count` in the response.** If it's 0, the push was sent to no devices (unlikely but possible in race conditions). If it's > 1, the user has multiple devices.
- **Don't retry on `401`.** Fix your `server_key` configuration — retrying won't help.

---

## Rate Limits & Retry Strategy

| Status | Action |
|--------|--------|
| `200` / `201` | Success. No retry needed |
| `400` | Client error. Fix request, don't retry same payload |
| `401` | Auth error. Fix `server_key` config, don't retry |
| `404` | User has no push-enabled devices. Handle gracefully, don't retry |
| `429` | Rate limited. Wait for `Retry-After` seconds, then retry |
| `503` | Server overloaded. Wait 1-5 seconds, then retry with exponential backoff |
| Network error | Retry with `dedupe_key` to prevent duplicates |

Recommended: exponential backoff with jitter, max 3 retries for `503` and network errors.
