API Reference

Complete REST API for sending push notifications, managing campaigns, tracking analytics, and audience targeting. Build powerful integrations with just a few API calls.

20+
Endpoints
REST
JSON API
99.9%
Uptime SLA
<50ms
Response Time
API access requires a Pro plan or higher. View plans | Upgrade now

Authentication

All API requests require authentication using your API token. Generate one from your dashboard under Profile → API Token.

Include your token in the request header:

X-API-Key: fp_your_api_token_here

// Alternative: Bearer token
Authorization: Bearer fp_your_api_token_here
Your API token starts with fp_ and is 67 characters long. Keep it secret and never expose it in client-side code.

Base URL

All API requests should be made to:

https://fyre-push.com/api/v1

Error Handling

The API uses standard HTTP status codes and returns errors in a consistent JSON format.

StatusCodeMeaning
200OKRequest succeeded
400Bad RequestInvalid parameters or missing required fields
401UnauthorizedMissing or invalid API token
403ForbiddenFeature not available on your plan
404Not FoundResource not found
429Rate LimitedToo many requests or daily limit exceeded
500Server ErrorInternal server error
{ "success": false, "error": "Description of what went wrong" }

Rate Limits & Plan Quotas

API usage is governed by your subscription plan. Exceeding limits returns a 429 response.

PlanWebsitesSubscribersDaily CampaignsMonthly CampaignsAPI Access
Loading plans...

Send Notification

Send a push notification to one or more websites/apps, with optional targeting by country, device type, and platform (web, Android, iOS).

POST /api/v1/notify

Request Body

ParameterTypeRequiredDescriptionExample
titlestringYesNotification title"New Update Available!"
bodystringNoNotification message body"Check out our latest features"
website_idintegerConditionalSingle website ID (required unless website_ids or send_to_all)1
website_idsinteger[]ConditionalArray of website IDs to target multiple sites[1, 2, 3]
send_to_allbooleanConditionalSend to all your websitestrue
urlstringNoURL to open when notification is clicked"https://example.com/update"
iconstringNoIcon URL (recommended 192x192 PNG)"https://example.com/icon.png"
imagestringNoLarge image URL (recommended 1200x628)"https://example.com/banner.jpg"
prioritystringNo"normal" or "high" (default: normal)"high"
ttlintegerNoTime-to-live in seconds (default: 259200 = 3 days, max: 604800 = 7 days)3600
require_interactionbooleanNoKeep notification visible until user interactstrue
targetingobjectNoAudience targeting filters (see targeting){"countries":["EG"],"devices":["mobile"]}
platformsstring[]NoTarget platforms: "web", "android", "ios" (default: ["web"])["web","android","ios"]
dataobjectNoCustom key-value data (for deep links, etc.){"screen":"product","id":"123"}
androidobjectNoAndroid-specific settings{"channel_id":"promo","sound":"alert"}
iosobjectNoiOS-specific settings{"sound":"default","badge":1}

📱 Platform-Specific Settings

Android Settings (android object)

PropertyTypeDescriptionExample
channel_idstringAndroid notification channel ID"promotions"
soundstringCustom sound file name (without extension)"alert_tone"
colorstringNotification accent color (hex format)"#FF6B35"

iOS Settings (ios object)

PropertyTypeDescriptionExample
soundstringSound file name or "default""default"
badgeintegerApp badge number to display1
interruption_levelstring"passive", "active", "time-sensitive", or "critical""time-sensitive"

Custom Data (data object)

Pass arbitrary key-value pairs for deep linking and custom handling in your mobile app. All values are sent as strings in the FCM data payload.

{
  "data": {
    "screen": "product_detail",
    "product_id": "12345",
    "action": "open_tab",
    "promo_code": "SAVE20"
  }
}

Example

curl -X POST https://fyre-push.com/api/v1/notify \
  -H "Content-Type: application/json" \
  -H "X-API-Key: fp_your_token" \
  -d '{
    "title": "New Update Available!",
    "body": "Check out our latest features",
    "website_ids": [1, 2],
    "url": "https://example.com/update",
    "icon": "https://example.com/icon.png",
    "image": "https://example.com/banner.jpg",
    "priority": "high",
    "targeting": {
      "countries": ["EG", "SA", "AE"],
      "devices": ["mobile"]
    }
  }'
const response = await fetch('https://fyre-push.com/api/v1/notify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'fp_your_token'
  },
  body: JSON.stringify({
    title: 'New Update Available!',
    body: 'Check out our latest features',
    website_ids: [1, 2],
    url: 'https://example.com/update',
    targeting: {
      countries: ['EG', 'SA', 'AE'],
      devices: ['mobile']
    }
  })
});
const data = await response.json();
console.log(data);
$ch = curl_init('https://fyre-push.com/api/v1/notify');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-API-Key: fp_your_token'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'title' => 'New Update Available!',
        'body' => 'Check out our latest features',
        'website_ids' => [1, 2],
        'url' => 'https://example.com/update',
        'targeting' => [
            'countries' => ['EG', 'SA'],
            'devices' => ['mobile']
        ]
    ])
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
print_r($data);
import requests

response = requests.post(
    'https://fyre-push.com/api/v1/notify',
    headers={
        'Content-Type': 'application/json',
        'X-API-Key': 'fp_your_token'
    },
    json={
        'title': 'New Update Available!',
        'body': 'Check out our latest features',
        'website_ids': [1, 2],
        'url': 'https://example.com/update',
        'targeting': {
            'countries': ['EG', 'SA'],
            'devices': ['mobile']
        }
    }
)
print(response.json())

Response

{
  "success": true,
  "message": "Notification sent successfully",
  "campaign_id": "42",
  "stats": {
    "total_targets": 5,
    "successful": 5,
    "failed": 0
  },
  "websites": [1, 2]
}

🧱 Complete Request Builder

Here's every parameter you can use when sending a notification:

// 🚀 Full notification with all options
{
  // ✅ REQUIRED - At least title is required
  "title": "Your Notification Title",
  
  // 💬 CONTENT - Optional but recommended
  "body": "Your notification message here",
  "url": "https://example.com/landing-page",
  "icon": "https://example.com/icon.png",        // 192x192 recommended
  "image": "https://example.com/banner.jpg",     // 1200x628 recommended
  
  // 🌐 WEBSITE TARGETING - Choose ONE option:
  "website_id": 1,                 // Single website
  // OR
  "website_ids": [1, 2, 3],      // Multiple websites
  // OR
  "send_to_all": true,            // All your websites
  
  // 📱 PLATFORM TARGETING - Optional (default: web only)
  "platforms": ["web", "android", "ios"],  // Target platforms
  
  // 🎯 AUDIENCE TARGETING - Optional filters
  "targeting": {
    "countries": ["EG", "SA", "AE"],  // ISO country codes
    "devices": ["mobile", "desktop"]  // Device types
  },
  
  // ⚙️ DELIVERY OPTIONS - Optional settings
  "priority": "high",             // "normal" or "high"
  "ttl": 86400,                    // Time-to-live in seconds (max 604800 = 7 days)
  "require_interaction": true,     // Keep visible until clicked
  
  // 📦 CUSTOM DATA - For deep links & mobile app handling
  "data": {
    "screen": "product_detail",     // Deep link screen
    "product_id": "12345"           // Custom payload
  },
  
  // 🤖 ANDROID-SPECIFIC - Optional
  "android": {
    "channel_id": "promotions",     // Notification channel
    "sound": "alert_tone",          // Custom sound
    "color": "#FF6B35"              // Accent color
  },
  
  // 🍎 iOS-SPECIFIC - Optional
  "ios": {
    "sound": "default",              // Sound file or "default"
    "badge": 1,                      // App badge count
    "interruption_level": "time-sensitive"  // iOS 15+ priority
  }
}

💡 Quick Examples

Minimal: title + website_id
Standard: title + body + url + website_id
Rich: + icon + image
Targeted: + targeting object
Multi-Platform: + platforms + android/ios
Deep Link: + data (custom key-value)

📊 Track Events

Track notification delivery and engagement events from your mobile apps. Use this to report when notifications are delivered, displayed, opened, clicked, or dismissed.

POST /api/v1/track

Request Body

ParameterTypeRequiredDescriptionExample
campaign_idstringYesThe campaign ID from the notification payload"42"
eventstringYesEvent type: delivered, displayed, opened, clicked, dismissed"opened"
platformstringNoPlatform: web, android, ios (default: web)"android"
website_idintegerNoWebsite/app ID for the event1

Batch Events

Send multiple events at once using the events array (max 100 per request):

{
  "events": [
    { "campaign_id": "42", "event": "delivered", "platform": "android" },
    { "campaign_id": "42", "event": "displayed", "platform": "android" },
    { "campaign_id": "43", "event": "clicked", "platform": "ios" }
  ]
}

Example (Single Event)

curl -X POST https://fyre-push.com/api/v1/track \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "42",
    "event": "opened",
    "platform": "android"
  }'
// In your FirebaseMessagingService
fun trackEvent(campaignId: String, event: String) {
    val url = URL("https://fyre-push.com/api/v1/track")
    val json = JSONObject().apply {
        put("campaign_id", campaignId)
        put("event", event)
        put("platform", "android")
    }
    val conn = url.openConnection() as HttpURLConnection
    conn.requestMethod = "POST"
    conn.setRequestProperty("Content-Type", "application/json")
    conn.outputStream.write(json.toString().toByteArray())
    conn.responseCode // trigger request
}
// In your UNUserNotificationCenterDelegate
func trackEvent(campaignId: String, event: String) {
    var request = URLRequest(url: URL(string: "https://fyre-push.com/api/v1/track")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    let body: [String: Any] = [
        "campaign_id": campaignId,
        "event": event,
        "platform": "ios"
    ]
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)
    URLSession.shared.dataTask(with: request).resume()
}
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';

Future<void> trackEvent(String campaignId, String event) async {
  await http.post(
    Uri.parse('https://fyre-push.com/api/v1/track'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'campaign_id': campaignId,
      'event': event,
      'platform': Platform.isAndroid ? 'android' : 'ios',
    }),
  );
}

Response

{
  "success": true,
  "message": "Event tracked successfully"
}

Event Types

EventDescriptionWhen to Send
deliveredNotification received by the deviceIn onMessageReceived / didReceiveRemoteNotification
displayedNotification shown to the userAfter displaying the notification
openedApp opened via notification tapIn notification tap handler
clickedUser clicked notification actionOn action button click or notification tap
dismissedUser dismissed the notificationIn onNotificationDismissed / swipe away
No authentication required: The track endpoint does not require an API key. This allows your mobile apps to report events directly without embedding your API key in the app binary.

📈 Platform Stats

Get per-platform engagement stats for a specific campaign, broken down by event type.

GET /api/v1/track/stats?campaign_id={id}
ParameterTypeRequiredDescriptionExample
campaign_idstringYesCampaign ID to get stats for"42"
{
  "success": true,
  "campaign_id": "42",
  "platforms": {
    "web": { "clicked": 120 },
    "android": {
      "delivered": 500,
      "displayed": 480,
      "opened": 200,
      "clicked": 150,
      "dismissed": 80
    },
    "ios": {
      "delivered": 300,
      "opened": 180,
      "clicked": 90
    }
  },
  "totals": {
    "delivered": 800,
    "displayed": 480,
    "opened": 380,
    "clicked": 360,
    "dismissed": 80
  }
}

List Campaigns

Retrieve a list of your notification campaigns with delivery stats.

GET /api/v1/campaigns
ParameterTypeRequiredDescriptionExample
website_idintegerNoFilter by specific website1
statusstringNosent, failed (default: all)"sent"
{
  "success": true,
  "notifications": [
    {
      "id": 1,
      "campaign_id": "42",
      "title": "New Update!",
      "body": "Check it out",
      "status": "sent",
      "sent_at": "2026-02-05T10:00:00Z",
      "subscribers_count": 500,
      "website_name": "My Site"
    }
  ]
}

Get Campaign Details

Get detailed information about a specific campaign including targeting, stats, and delivery data.

GET /api/v1/campaigns/{campaign_id}
{
  "success": true,
  "campaign": {
    "campaign_id": "42",
    "title": "New Update!",
    "body": "Check it out",
    "click_url": "https://example.com",
    "icon_url": "https://example.com/icon.png",
    "status": "sent",
    "targeting": { "countries": ["EG"], "devices": ["mobile"] },
    "subscribers_count": 500,
    "clicks_count": 30,
    "sent_at": "2026-02-05T10:00:00Z"
  }
}

Campaign Stats

Get real-time delivery and engagement analytics for campaigns.

GET /api/v1/campaign-stats?campaign_id={id}
ParameterTypeRequiredDescriptionExample
campaign_idstringNoSpecific campaign (omit for all, max 200)"42"
{
  "campaigns": [
    {
      "campaign_id": "42",
      "title": "New Update!",
      "delivered": 500,
      "clicked": 30,
      "ctr": "6.00",
      "created_at": "2026-02-05T10:00:00Z"
    }
  ]
}

Delete Campaign

Permanently delete a sent or failed campaign and its associated data.

DELETE /api/v1/campaigns/{campaign_id}
{ "success": true, "message": "Campaign deleted successfully" }

Targeting Options

Send notifications to specific audience segments by filtering on country and device type. Pass a targeting object in your send request. All filters are optional - omit them to send to everyone.

Available Filters

FilterTypeAvailable ValuesDescription
countriesstring[] Arab: EG SA AE KW QA BH OM JO LB IQ SY PS YE LY TN DZ MA SD
Other: US GB DE FR TR IN + all ISO 3166-1 alpha-2 codes
Target specific countries using ISO codes
devicesstring[]mobile desktop tabletTarget specific device types

📝 Targeting Scenarios

✅ Send to Everyone (No Targeting)

{
  "title": "New Article Published",
  "body": "Check out our latest post",
  "send_to_all": true,
  "url": "https://example.com/blog"
}

🇪🇬 Send to Egypt Only

{
  "title": "Egypt Exclusive Offer",
  "body": "50% discount for our Egyptian customers",
  "website_id": 1,
  "url": "https://example.com/egypt-offer",
  "targeting": {
    "countries": ["EG"]
  }
}

🇸🇦🇦🇪🇰🇼 Send to Gulf Countries

{
  "title": "Gulf Region Sale",
  "body": "Special prices for SA, UAE, Kuwait",
  "website_ids": [1, 2],
  "url": "https://example.com/gulf-sale",
  "targeting": {
    "countries": ["SA", "AE", "KW", "QA", "BH", "OM"]
  }
}

📱 Mobile Users Only

{
  "title": "Download Our App",
  "body": "Get the mobile app for a better experience",
  "send_to_all": true,
  "url": "https://example.com/app",
  "targeting": {
    "devices": ["mobile"]
  }
}

🖥️ Desktop Users Only

{
  "title": "Desktop Software Update",
  "body": "New version available for desktop",
  "send_to_all": true,
  "url": "https://example.com/download",
  "targeting": {
    "devices": ["desktop"]
  }
}

🎯 Combined: Mobile users in Egypt & Saudi Arabia

{
  "title": "Flash Sale!",
  "body": "Limited time offer - 50% off",
  "send_to_all": true,
  "url": "https://example.com/sale",
  "image": "https://example.com/sale-banner.jpg",
  "targeting": {
    "countries": ["EG", "SA"],
    "devices": ["mobile"]
  }
}

💻📱 All devices except tablets

{
  "title": "General Update",
  "body": "Important news for desktop and mobile users",
  "send_to_all": true,
  "url": "https://example.com/news",
  "targeting": {
    "devices": ["desktop", "mobile"]
  }
}
AND Logic: When you specify both countries and devices, only subscribers matching both criteria will receive the notification. For example, {"countries":["EG"],"devices":["mobile"]} = only mobile users in Egypt.
Note: Browser and OS filtering is not supported when sending. You can only target by countries and devices. Use the Audience Data endpoint to view browser/OS analytics.

📱 Platform Targeting Examples

🤖🍎 Send to All Mobile Apps

{
  "title": "App Update Available",
  "body": "Version 2.0 is here with new features!",
  "send_to_all": true,
  "platforms": ["android", "ios"],
  "data": {
    "screen": "update",
    "version": "2.0"
  }
}

🌐📱 Cross-Platform: Web + Android + iOS

{
  "title": "Flash Sale 🔥",
  "body": "50% off everything for 24 hours",
  "send_to_all": true,
  "url": "https://example.com/sale",
  "image": "https://example.com/sale-banner.jpg",
  "platforms": ["web", "android", "ios"],
  "priority": "high",
  "data": {
    "screen": "sale_page",
    "promo_code": "FLASH50"
  },
  "android": {
    "channel_id": "promotions",
    "sound": "sale_alert",
    "color": "#FF6B35"
  },
  "ios": {
    "sound": "default",
    "badge": 1,
    "interruption_level": "time-sensitive"
  }
}

🇪🇬🤖 Android Users in Egypt

{
  "title": "Egypt Android Exclusive",
  "body": "Special offer for our Egyptian Android users",
  "website_id": 1,
  "platforms": ["android"],
  "targeting": {
    "countries": ["EG"]
  },
  "data": {
    "screen": "offer",
    "offer_id": "eg_special_2026"
  },
  "android": {
    "channel_id": "offers"
  }
}

Get Audience Data

Retrieve your subscriber breakdown by country, device, browser, and OS. Use this to understand your audience before sending targeted notifications.

GET /api/v1/targeting-data
ParameterTypeRequiredDescriptionExample
website_idsstringNoComma-separated website IDs or "all""1,2,3"
countriesstringNoComma-separated country codes to filter"EG,SA"
{
  "success": true,
  "data": {
    "total_subscribers": 15000,
    "countries": [{ "value": "EG", "count": 5000 }],
    "browsers": [{ "value": "chrome", "count": 10000 }],
    "devices": [{ "value": "mobile", "count": 8000 }],
    "os": [{ "value": "android", "count": 6000 }]
  }
}
Browser and OS data is returned for analytics purposes. When sending notifications, only countries and devices can be used as targeting filters.

Estimate Segment Size

Get an estimated subscriber count for a specific targeting combination before sending.

POST /api/v1/segment-count
ParameterTypeRequiredDescriptionExample
websitesinteger[]NoWebsite IDs to scope[1, 2]
countriesstring[]NoCountry codes["EG", "SA"]
devicesstring[]NoDevice types["mobile"]
{
  "success": true,
  "count": 3500,
  "estimated_count": 3500,
  "is_exact": true
}

Saved Segments

Save, list, and delete reusable audience segments for repeated targeting.

GET /api/v1/segments

Returns all your saved segments.

POST /api/v1/segments
ParameterTypeRequiredDescriptionExample
namestringYesUnique segment name"Egypt Mobile Users"
websitesinteger[]NoWebsite IDs[1, 2]
countriesstring[]NoCountry codes["EG"]
devicesstring[]NoDevice types["mobile"]
DELETE /api/v1/segments?id={segment_id}

Delete a saved segment by ID.

Account Info

Get your account details and usage statistics.

GET /api/v1/account
{
  "success": true,
  "account": {
    "membership_number": "FP-12345",
    "username": "johndoe",
    "email": "john@example.com",
    "name": "John Doe",
    "timezone": "auto",
    "language": "en"
  },
  "stats": {
    "websites": 3,
    "total_subscribers": 15000,
    "total_campaigns": 120
  }
}

List Websites

Get all your registered websites with subscriber counts and verification status.

GET /api/v1/websites
{
  "success": true,
  "websites": [
    {
      "id": 1,
      "name": "My Site",
      "url": "https://example.com",
      "is_verified": 1,
      "subscriber_count": 5000
    }
  ]
}

Subscriber Analytics

Get subscriber breakdown by country and device.

GET /api/v1/subscribers?website_id={id}
ParameterTypeRequiredDescriptionExample
website_idintegerNoFilter by specific website1
{
  "success": true,
  "total_subscribers": 15000,
  "by_country": [{ "country": "EG", "count": 5000 }],
  "by_device": [{ "device": "mobile", "count": 8000 }]
}

Analytics & Statistics

Get comprehensive analytics including daily subscriber growth, recent campaign performance, and geographic distribution.

GET /api/v1/stats
ParameterTypeRequiredDescriptionExample
website_idintegerNoFilter by specific website1
daysintegerNoLookback period (default: 30, max: 90)30
{
  "success": true,
  "stats": {
    "daily_subscribers": [{ "date": "2026-02-05", "new_subscribers": 50 }],
    "recent_campaigns": [{
      "title": "New Update!",
      "status": "sent",
      "sent_count": 500,
      "delivered_count": 480,
      "clicked_count": 30
    }],
    "top_countries": [{ "country": "EG", "count": 5000 }]
  }
}

⚡ API Tester

Test your API key and endpoints directly from this page. Enter your API key, select an endpoint, and click Test to see live results.

Your API key is sent directly to the Fyre Push API and is never stored or logged by this page. API calls from the tester count toward your rate limits.

Plans & Features

API access is available on Pro plan and above. Here's what each plan includes:

Send notifications via API
Smart targeting (country & device)
Multi-platform: Web, Android, iOS
Campaign management & deletion
Real-time analytics (delivery, clicks, CTR)
Per-platform event tracking
Saved audience segments
Multi-site management
Deep linking & custom data
Subscriber analytics (geo, device)

View Plans & Pricing →

WordPress Integration

Send push notifications automatically when you publish a new WordPress post.

// Add to your theme's functions.php
function fyre_push_on_publish($post_id) {
    $post = get_post($post_id);
    if ($post->post_type !== 'post') return;

    $thumbnail = get_the_post_thumbnail_url($post_id, 'medium');

    wp_remote_post('https://fyre-push.com/api/v1/notify', [
        'headers' => [
            'Content-Type' => 'application/json',
            'X-API-Key'    => 'fp_your_token_here',
        ],
        'body' => json_encode([
            'title'      => $post->post_title,
            'body'       => wp_trim_words($post->post_content, 20),
            'url'        => get_permalink($post_id),
            'image'      => $thumbnail ?: '',
            'send_to_all' => true,
        ]),
        'timeout' => 15,
    ]);
}
add_action('publish_post', 'fyre_push_on_publish');

SDKs & Quick Start

Use our API with any HTTP client. Here’s a quick JavaScript class to get started:

class FyrePush {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://fyre-push.com/api/v1';
  }

  async request(method, path, body = null) {
    const opts = {
      method,
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey
      }
    };
    if (body) opts.body = JSON.stringify(body);
    const res = await fetch(`${this.baseUrl}${path}`, opts);
    return res.json();
  }

  send(data) { return this.request('POST', '/notify', data); }
  getAccount() { return this.request('GET', '/account'); }
  getWebsites() { return this.request('GET', '/websites'); }
  getStats(days = 30) { return this.request('GET', `/stats?days=${days}`); }
  getCampaigns() { return this.request('GET', '/campaigns'); }
  deleteCampaign(id) { return this.request('DELETE', `/campaigns/${id}`); }
  getTargetingData(ids) { return this.request('GET', ids ? `/targeting-data?website_ids=${ids}` : '/targeting-data'); }
  getSegmentCount(f) { return this.request('POST', '/segment-count', f); }
}

// Usage
const fp = new FyrePush('fp_your_token');
await fp.send({
  title: 'Hello!',
  body: 'New article published',
  send_to_all: true,
  url: 'https://example.com/article'
});
class FyrePush {
    private $apiKey;
    private $baseUrl = 'https://fyre-push.com/api/v1';

    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }

    private function request($method, $path, $body = null) {
        $ch = curl_init($this->baseUrl . $path);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'X-API-Key: ' . $this->apiKey,
            ],
        ]);
        if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        $res = curl_exec($ch);
        curl_close($ch);
        return json_decode($res, true);
    }

    public function send($data) { return $this->request('POST', '/notify', $data); }
    public function getAccount() { return $this->request('GET', '/account'); }
    public function getWebsites() { return $this->request('GET', '/websites'); }
    public function getCampaigns() { return $this->request('GET', '/campaigns'); }
    public function deleteCampaign($id) { return $this->request('DELETE', "/campaigns/$id"); }
}

// Usage
$fp = new FyrePush('fp_your_token');
$result = $fp->send([
    'title' => 'Hello!',
    'body' => 'New article published',
    'send_to_all' => true,
    'url' => 'https://example.com/article'
]);