# PHP Social Media API SDK

The official PHP SDK. PHP 8.1+, no Composer dependencies (ext-curl, ext-json),
Stripe-style resource objects with associative-array params.

## Install

```bash
composer require omnisocials/omnisocials-php
```

## Authentication

```php
use OmniSocials\Client;

$client = new Client(apiKey: 'omsk_live_...');
```

Reads `OMNISOCIALS_API_KEY` if omitted. A missing key throws
`OmniSocials\Exception\AuthenticationException`.

## Create a post

```php
$response = $client->posts->create([
    'content' => 'New drop this Friday',
    'channels' => ['instagram', 'facebook', 'linkedin'],
    'scheduled_at' => '2026-08-01T09:00:00Z',
    'media_urls' => ['https://example.com/teaser.jpg'],
]);
echo $response['data']['id'] . ' ' . $response['data']['status'];
```

Any `media_urls` (or `media_ids`) entry can also be an array carrying per-media
alt text (max 1500 chars), delivered to Mastodon, Bluesky, X (photos/GIFs), and
Pinterest:

```php
'media_urls' => [['url' => 'https://example.com/teaser.jpg', 'alt' => 'Red sneaker on a white background']],
```

Publish immediately:

```php
$client->posts->createAndPublish([
    'content' => 'Going live right now',
    'channels' => ['x', 'bluesky'],
]);
```

## Upload media

```php
$upload = $client->media->uploadFromUrl([
    'url' => 'https://example.com/launch-video.mp4',
    'name' => 'launch-video-v2',
    'folder' => 'Campaigns',
]);
echo $upload['data']['id'];
print_r($upload['compatibility']);
```

Local files via multipart:

```php
// From a path
$client->media->upload(['file' => './photos/product.jpg', 'name' => 'product-hero']);

// Or from raw bytes (pass a filename so the API can detect the type)
$bytes = file_get_contents('./photos/product.jpg');
$client->media->upload(['file' => $bytes, 'filename' => 'product.jpg']);
```

## Analytics

```php
// One post's latest per-platform metrics
$stats = $client->analytics->post('post_id');
print_r($stats['data']['platforms']['instagram']['metrics'] ?? null);

// Batch: up to 100 posts in one call
$batch = $client->analytics->posts(['id1', 'id2', 'id3']);

// Workspace-wide overview
$overview = $client->analytics->overview(['period' => '30d']);
echo $overview['data']['total_impressions'] . ' ' . $overview['data']['total_engagements'];

// Account-level stats (followers etc)
$accountStats = $client->analytics->accounts(['platform' => 'instagram']);
```

## Social Inbox

The PHP SDK also covers the Social Inbox (cursor-paginated):

```php
$conversations = $client->inbox->listConversations(['platform' => 'instagram', 'unread' => true]);
$messages = $client->inbox->getMessages($conversationId);
$client->inbox->reply($conversationId, ['text' => 'Thanks for reaching out!']);
$client->inbox->markRead($conversationId);
```

Requires the opt-in `inbox:read` / `inbox:write` scopes. See the
[Social Inbox guide](/inbox).

## Verify webhooks

```php
<?php

require __DIR__ . '/vendor/autoload.php';

use OmniSocials\Webhooks;
use OmniSocials\Exception\WebhookVerificationException;

$payload = file_get_contents('php://input'); // the raw body, untouched
$signature = $_SERVER['HTTP_X_OMNISOCIALS_SIGNATURE'] ?? '';

try {
    $event = Webhooks::verifySignature(
        $payload,
        $signature,
        getenv('OMNISOCIALS_WEBHOOK_SECRET'),
        300 // tolerance in seconds (default)
    );
} catch (WebhookVerificationException $e) {
    http_response_code(400);
    exit;
}

switch ($event['type']) {
    case 'post.published':
        error_log('Published: ' . $event['data']['post_id']);
        break;
    case 'post.failed':
        error_log('Failed: ' . $event['data']['post_id']);
        break;
}

http_response_code(200);
```

## Errors

The base class is `OmniSocials\Exception\OmniSocialsException`. `ApiException`
exposes `getStatus()`, `getErrorCode()`, `getMessage()`, and `getBody()`. Types:
`ValidationException` (400/422), `AuthenticationException` (401),
`PermissionDeniedException` (403), `NotFoundException` (404), `RateLimitException`
(429, `getRetryAfter()`), `ServerException` (5xx), `ApiConnectionException`, and
`WebhookVerificationException`.

```php
use OmniSocials\Exception\ApiConnectionException;
use OmniSocials\Exception\ApiException;
use OmniSocials\Exception\RateLimitException;
use OmniSocials\Exception\ValidationException;

try {
    $client->posts->create(['content' => 'Hi', 'channels' => ['instagram']]);
} catch (RateLimitException $e) {
    echo 'Rate limited, retry in ' . $e->getRetryAfter() . "s\n";
} catch (ValidationException $e) {
    echo "Bad request ({$e->getErrorCode()}): {$e->getMessage()}\n";
    print_r($e->getBody());
} catch (ApiConnectionException $e) {
    echo 'Network problem: ' . $e->getMessage() . "\n";
} catch (ApiException $e) {
    echo "API error {$e->getStatus()} ({$e->getErrorCode()}): {$e->getMessage()}\n";
}
```

Note the error code accessor is `getErrorCode()`, not `getCode()`.

## Configuration

```php
$client = new Client(
    apiKey: 'omsk_live_...',
    baseUrl: 'https://api.omnisocials.com/v1', // default
    timeout: 30.0,   // per-request timeout in seconds (default 30)
    maxRetries: 2,   // automatic retries on 429 / 5xx / network errors (default 2)
);
```

## Notes

- Responses are decoded to associative arrays
- Webhook verification is the static `Webhooks::verifySignature()` using `hash_equals`
- Source: [github.com/OmniSocials/omnisocials-php](https://github.com/OmniSocials/omnisocials-php)
