fopost / sdk
Official PHP SDK for the FoPost API.
Package info
pkg:composer/fopost/sdk
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^3.9
This package is auto-updated.
Last update: 2026-08-30 21:48:20 UTC
README
The official PHP SDK for the FoPost API. Connect social accounts once, then compose, schedule, and publish from your own application.
Requires PHP 8.1 or newer, plus ext-curl and ext-json. Nothing else: no framework, no HTTP library.
Install
composer require fopost/sdk
Get an API key
Create a key at app.fopost.com/api-keys. The full API reference lives at fopost.com/docs.
Quickstart
<?php require __DIR__ . '/vendor/autoload.php'; use Fopost\Sdk\Client; $client = new Client('fp_...'); // or set FOPOST_API_KEY $workspace = $client->workspaces()->list()[0]; $accounts = $client->accounts()->list($workspace->id); $post = $client->posts()->create( workspaceId: $workspace->id, content: 'Hello from PHP', accounts: array_map(fn ($a) => $a->id, $accounts), ); $client->posts()->publish($post->id);
The key falls back to the FOPOST_API_KEY environment variable, so new Client() works when it is set.
$client = new Client( apiKey: 'fp_...', baseUrl: 'https://api.fopost.com', // a bare host gets /v1 appended timeout: 30.0, // seconds maxRetries: 3, // attempts, on 429 only );
Posts
use DateTimeImmutable; // List one page. The result iterates over its items directly. $page = $client->posts()->list(workspaceId: $workspaceId, status: 'scheduled'); foreach ($page as $post) { echo $post->id, ' ', $post->status, PHP_EOL; } echo $page->meta->total; // Walk every matching post, one page at a time. foreach ($client->posts()->iterate(workspaceId: $workspaceId) as $post) { echo $post->text(), PHP_EOL; } $post = $client->posts()->get('p_123'); // Create a draft, a thread, or a scheduled post. $draft = $client->posts()->create( workspaceId: $workspaceId, content: ['First post', 'The reply'], accounts: ['acc_1', 'acc_2'], ); $scheduled = $client->posts()->create( workspaceId: $workspaceId, content: 'Going out on Monday', accounts: ['acc_1'], status: 'scheduled', scheduleAt: new DateTimeImmutable('2026-03-01T09:00:00Z'), ); // Partial update: only the fields you name are sent. $client->posts()->update($draft->id, title: 'A better title'); $client->posts()->schedule($draft->id, new DateTimeImmutable('+1 day')); $client->posts()->unschedule($draft->id); $client->posts()->publish($draft->id); $client->posts()->preflight($draft->id); $client->posts()->retry($draft->id); $client->posts()->cancel($draft->id); $client->posts()->delete($draft->id); foreach ($client->posts()->deliveries($draft->id) as $delivery) { echo $delivery->platform, ' ', $delivery->status, ' ', $delivery->externalUrl, PHP_EOL; }
Accounts
$accounts = $client->accounts()->list($workspaceId); $account = $client->accounts()->get('acc_1'); $health = $client->accounts()->health('acc_1'); $client->accounts()->disconnect('acc_1');
Workspaces
$workspaces = $client->workspaces()->list(); $workspace = $client->workspaces()->get($workspaceId); echo $workspace->name, ' ', $workspace->timezone, PHP_EOL;
Labels
$labels = $client->labels()->list($workspaceId); $label = $client->labels()->create($workspaceId, 'Product launch', '#0070f3'); $client->labels()->update($label->id, name: 'Launch week'); $client->labels()->delete($label->id);
AI
Every AI call spends AI credits.
$balance = $client->ai()->credits(); echo $balance->creditsRemaining, ' of ', $balance->creditsTotal, PHP_EOL; $caption = $client->ai()->generateCaption( currentCaption: 'new feature is live', platforms: ['linkedin', 'bluesky'], charLimit: 280, ); $rewrite = $client->ai()->rewrite('One draft, many networks', ['linkedin', 'bluesky'], tone: 'friendly'); foreach ($rewrite->results as $variant) { echo $variant->platform, ': ', $variant->content, PHP_EOL; } $repurposed = $client->ai()->repurposeUrl('https://example.com/blog/launch', ['linkedin', 'threads']);
Errors
Every non-2xx response raises an exception under Fopost\Sdk\Exception.
| Status | Exception |
|---|---|
| 400, 422 | ValidationException |
| 401 | AuthenticationException |
| 402 | PaymentRequiredException |
| 403 | PermissionDeniedException |
| 404 | NotFoundException |
| 429 | RateLimitException |
| anything else | ApiException |
All of them extend FopostException, which carries getStatus(), getErrorCode(), getMessage(), and getBody().
use Fopost\Sdk\Exception\FopostException; use Fopost\Sdk\Exception\RateLimitException; use Fopost\Sdk\Exception\ValidationException; try { $client->posts()->publish('p_123'); } catch (ValidationException $e) { print_r($e->getErrors()); } catch (RateLimitException $e) { echo 'retry in ', $e->getRetryAfter(), 's', PHP_EOL; } catch (FopostException $e) { echo $e->getStatus(), ' ', $e->getMessage(), PHP_EOL; }
A 429 is retried automatically, up to maxRetries attempts, waiting for the interval the API asks for in Retry-After (capped at 60 seconds). The exception is raised only when the last attempt still comes back rate limited.
Anything the SDK does not wrap yet
$body = $client->request('GET', '/some/new/endpoint', params: ['workspace_id' => $workspaceId]);
Testing your integration
The transport is an interface, so nothing has to reach the network in your test suite.
use Fopost\Sdk\Client; use Fopost\Sdk\Http\Response; use Fopost\Sdk\Http\Transport; $fake = new class implements Transport { public function send(string $method, string $url, array $headers, ?string $body): Response { return new Response(200, [], json_encode(['data' => []])); } }; $client = new Client('fop_test_key', Client::DEFAULT_BASE_URL, 30.0, 3, $fake);
Support
Questions and issues: fopost.com/contact or the issue tracker.
License
MIT. Copyright Porter Bridge, LLC.