Usage
This extension describes the protocol between the fixpunkt social server (server side) and a TYPO3 instance such as fp_social (client side). Both sides use the same classes, so that creation and evaluation are guaranteed to use the same format.
Request flow
- The client side (e.g.
fp_social) requests posts or the available accounts from the social server. -
The social server gathers the data and creates a response object. Which of the four types is created depends on the result:
- a single post → SocialServerPostResponse
- multiple posts (with pagination) → SocialServerPostsResponse
- the connected accounts → SocialServerAccountsResponse
- an error → SocialServerErrorResponse
- The server serializes the object via
toArray()andjson_encode()and sends the JSON to the client. - The client passes the JSON to the factory SocialServerResponse::fromJson() and receives the matching, typed object back.
Every response carries the fully qualified class name in the type field and
the protocol version (currently 2) in the version field. Based on these
two fields the factory decides which object to reconstruct and checks version
compatibility.
Note
All of the following examples use anonymized sample data (example.com,
fictional IDs). The format matches that of real responses.
Structure of a post object
Before we get to the responses, it is worth looking at the JSON of a single Post, since some fields have a particular format here:
| Field | Format |
|---|---|
headline | Headline. Empty ("") for many sources (e.g. Facebook). |
message | HTML text. May contain <br /> and <a> tags as well as emoji
placeholders of the form {emoji:9728} (Unicode code point). |
update_time | Serialized \DateTime object with the keys date,
timezone_type and timezone. |
hashtags | List of strings without a leading # (e.g. "summer"). |
mentions | List of objects with displayName and systemName; empty when
there are no mentions. |
pictures | List of picture URLs. |
Example 1: A single post (SocialServerPostResponse)
Server side – create and output as JSON:
use Fixpunkt\FpSocialBridge\v2\Data\Post;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$post = new Post(
id: '100000000000001_200000000000001',
headline: '',
message: 'Summertime! Here is our favourite recipe for hot days.<br />'
. "\n" . 'Have fun trying it out {emoji:9728} '
. '<a href=\'https://social.example.com/hashtag/recipe\'>#recipe</a>',
post_url: 'https://social.example.com/100000000000001/posts/200000000000001',
update_time: new \DateTime('2026-06-27 08:00:38+00:00'),
link: 'https://social.example.com/100000000000001/posts/200000000000001',
hashtags: ['recipe', 'summer', 'drinks', 'tip'],
mentions: [],
pictures: ['https://cdn.example.com/media/image-1.jpg'],
);
$response = new SocialServerPostResponse(SocialServerResponse::version, $post);
echo json_encode($response->toArray());
The resulting JSON (values shortened):
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerPostResponse",
"version": 2,
"post": {
"id": "100000000000001_200000000000001",
"headline": "",
"message": "Summertime! ...<br />\n... <a href='...'>#recipe</a>",
"post_url": "https://social.example.com/100000000000001/posts/200000000000001",
"update_time": {
"date": "2026-06-27 08:00:38.000000",
"timezone_type": 1,
"timezone": "+00:00"
},
"link": "https://social.example.com/100000000000001/posts/200000000000001",
"hashtags": ["recipe", "summer", "drinks", "tip"],
"mentions": [],
"pictures": ["https://cdn.example.com/media/image-1.jpg"]
}
}
Client side – evaluate:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerPostResponse) {
$post = $response->getPost();
echo $post->getMessage();
foreach ($post->getHashtags() as $hashtag) {
echo '#' . $hashtag; // the leading # is not part of the value
}
}
Note
If a post contains mentions, the mentions field looks like this:
"mentions": [
{"displayName": "Example Organization", "systemName": "300000000000001"}
]
Example 2: Multiple posts with pagination (SocialServerPostsResponse)
This is the most common response: a list of posts plus the cursors for paging.
previousPage is empty on the first page; nextPage contains the full URL
for the next fetch (or is empty when no further page exists).
Server side – create and output as JSON:
use Fixpunkt\FpSocialBridge\v2\Data\Post;
use Fixpunkt\FpSocialBridge\v2\Data\Posts;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$posts = new Posts([
new Post(
id: '100000000000001_200000000000001',
headline: '',
message: 'Summertime! Here is our favourite recipe for hot days.',
post_url: 'https://social.example.com/100000000000001/posts/200000000000001',
update_time: new \DateTime('2026-06-27 08:00:38+00:00'),
link: 'https://social.example.com/100000000000001/posts/200000000000001',
hashtags: ['recipe', 'summer', 'drinks', 'tip'],
mentions: [],
pictures: ['https://cdn.example.com/media/image-1.jpg'],
),
new Post(
id: '100000000000001_200000000000002',
headline: '',
message: 'We will soon present our new project – stay tuned!',
post_url: 'https://social.example.com/100000000000001/posts/200000000000002',
update_time: new \DateTime('2026-06-24 17:00:14+00:00'),
link: 'https://social.example.com/100000000000001/posts/200000000000002',
hashtags: ['project', 'news', 'outlook'],
mentions: [],
pictures: ['https://cdn.example.com/media/image-2.jpg'],
),
]);
$response = new SocialServerPostsResponse(
SocialServerResponse::version,
$posts,
previous: '',
next: 'https://social-server.example.com/networks/example/posts?tx_fpsocialserver_show%5Bafter%5D=QVFI...&tx_fpsocialserver_show%5Bversion%5D=2&cHash=0123456789abcdef0123456789abcdef',
);
echo json_encode($response->toArray());
The resulting JSON (posts and nextPage shortened):
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerPostsResponse",
"version": 2,
"posts": [
{
"id": "100000000000001_200000000000001",
"headline": "",
"message": "Summertime! ...",
"post_url": "https://social.example.com/100000000000001/posts/200000000000001",
"update_time": {
"date": "2026-06-27 08:00:38.000000",
"timezone_type": 1,
"timezone": "+00:00"
},
"link": "https://social.example.com/100000000000001/posts/200000000000001",
"hashtags": ["recipe", "summer", "drinks", "tip"],
"mentions": [],
"pictures": ["https://cdn.example.com/media/image-1.jpg"]
},
{
"id": "100000000000001_200000000000002",
"headline": "",
"message": "We will soon present our new project – stay tuned!",
"...": "..."
}
],
"requests": {
"previousPage": "",
"nextPage": "https://social-server.example.com/networks/example/posts?tx_fpsocialserver_show%5Bafter%5D=QVFI...&tx_fpsocialserver_show%5Bversion%5D=2&cHash=0123456789abcdef0123456789abcdef"
}
}
Client side – evaluate and iterate over the collection:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerPostsResponse) {
foreach ($response->getPosts() as $post) {
echo $post->getMessage();
}
// Cursors (URLs) for the next and previous page
$next = $response->getNext();
$previous = $response->getPrevious();
}
Note
Posts implements Iterator and Countable and
can therefore be iterated directly with foreach and counted with
count().
Example 3: Error response (SocialServerErrorResponse)
If an error occurs on the social server, it creates a SocialServerErrorResponse instead of a data response.
Server side – create and output as JSON:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerErrorResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = new SocialServerErrorResponse(
SocialServerResponse::version,
code: 42,
message: 'The requested account does not exist.',
);
echo json_encode($response->toArray());
The resulting JSON:
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerErrorResponse",
"version": 2,
"code": 555042,
"message": "The requested account does not exist."
}
Note
The supplied error code is combined with the prefix 5550 in the
constructor and stored as an int. So code: 42 becomes 555042.
Client side – evaluate:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerErrorResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerErrorResponse) {
throw new \RuntimeException(
$response->getMessage(),
$response->getCode()
);
}
Example 4: The available accounts (SocialServerAccountsResponse)
The endpoint POST /api/accounts returns every account the authenticated user
has connected on the social server, grouped by network. The client side uses it
to prefill the account, channel and label fields of an account record instead of
having editors type page ids by hand.
Only networks whose access is established on the server appear here – Facebook, Instagram, Instagram (direct login) and LinkedIn. Wordpress, Youtube and Bluesky are configured entirely on the client side and are therefore absent.
Server side – create and output as JSON:
use Fixpunkt\FpSocialBridge\v2\Data\Account;
use Fixpunkt\FpSocialBridge\v2\Data\Accounts;
use Fixpunkt\FpSocialBridge\v2\Data\Channel;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerAccountsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$accounts = new Accounts([
'Fixpunkt\FpSocialServer\Networks\Facebook\Connector' => [
new Account(
uid: 12,
network: 'Fixpunkt\FpSocialServer\Networks\Facebook\Connector',
networkKey: 'facebook',
networkName: 'Facebook',
displayName: 'Example Organization',
username: '100000000000001',
email: 'office@example.com',
expires: null,
expired: false,
channels: [
new Channel('200000000000001', 'Example Page'),
new Channel('200000000000002', 'Example Campaign'),
],
),
],
]);
$response = new SocialServerAccountsResponse(SocialServerResponse::version, $accounts);
echo json_encode($response->toArray());
The resulting JSON:
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerAccountsResponse",
"version": 2,
"accounts": {
"Fixpunkt\\FpSocialServer\\Networks\\Facebook\\Connector": [
{
"uid": 12,
"network": "Fixpunkt\\FpSocialServer\\Networks\\Facebook\\Connector",
"networkKey": "facebook",
"networkName": "Facebook",
"displayName": "Example Organization",
"username": "100000000000001",
"email": "office@example.com",
"expires": null,
"expired": false,
"channels": [
{"id": "200000000000001", "name": "Example Page"},
{"id": "200000000000002", "name": "Example Campaign"}
]
}
],
"Fixpunkt\\FpSocialServer\\Networks\\LinkedIn\\Connector": [
{
"uid": 15,
"network": "Fixpunkt\\FpSocialServer\\Networks\\LinkedIn\\Connector",
"networkKey": "linkedin",
"networkName": "LinkedIn",
"displayName": "Alex Example",
"username": "AbC123dEf",
"email": "alex@example.com",
"expires": {
"date": "2026-12-24 09:15:00.000000",
"timezone_type": 3,
"timezone": "Europe/Berlin"
},
"expired": false,
"channels": [
{"id": "urn:li:organization:300001", "name": "Example GmbH"}
]
}
]
}
}
Client side – evaluate:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerAccountsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerAccountsResponse) {
foreach ($response->getAccounts() as $network => $accounts) {
foreach ($accounts as $account) {
foreach ($account->getChannels() as $channel) {
// channel field ← getId(), label field ← getName()
echo $account->getDisplayName() . ': ' . $channel->getName();
}
}
}
}
Note
expires is null for networks whose access keys do not expire
(Facebook, Instagram). For LinkedIn and Instagram (direct login) it is a
serialized \DateTime in the same format as update_time on a post.
Note
Instagram reuses the Facebook access key. The same account therefore shows up
under both networks with the same uid but different channels –
Facebook pages in one case, Instagram business accounts in the other.
Handling all types together
In practice the client side does not know in advance which type will come back.
The factory always returns the matching object; instanceof is used to tell
them apart:
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerAccountsResponse;
use Fixpunkt\FpSocialBridge\v2\Response\SocialServerErrorResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerErrorResponse) {
throw new \RuntimeException(
$response->getMessage(),
$response->getCode()
);
}
if ($response instanceof SocialServerPostsResponse) {
foreach ($response->getPosts() as $post) {
echo $post->getMessage();
}
$next = $response->getNext(); // URL for the next page
}
if ($response instanceof SocialServerPostResponse) {
$post = $response->getPost();
}
if ($response instanceof SocialServerAccountsResponse) {
foreach ($response->getAccounts() as $network => $accounts) {
// ...
}
}
Note
fromJson() checks the protocol version and throws an \Exception if
the data is corrupted or the version does not match. See
SocialServerResponse.