Lean library extension providing the data-transfer and response objects
exchanged between the fixpunkt social server and TYPO3. It encapsulates the
protocol (currently version 2) as typed PHP objects and is used, among others,
by fp_social.
The extension deliberately contains no business logic of its own, no TCA and
no plugins – only the shared classes, so that the server and client side use the
same format.
fp_social_bridge is a lean library extension. It provides the
data-transfer and response objects exchanged between the fixpunkt social
server and TYPO3. The protocol (currently version 2) is encapsulated as typed
PHP objects and is used, among others, by
fp_social.
The extension deliberately contains no business logic of its own, no TCA and
no plugins – only the shared classes, so that the server and client side use the
same format.
Included classes
Class
Purpose
SerializableInterface
Contract: fromJson(), fromArray(), toArray()
v2\Data\Post
A single post (id, headline, message, url, date, hashtags,
mentions, pictures)
v2\Data\Posts
Iterable, countable collection of Post objects
v2\Data\Channel
A selectable page or channel of a connected account (id, name)
v2\Data\Account
A connected account incl. its channels (uid, network, display name,
e-mail, expiry)
v2\Data\Accounts
Iterable, countable collection of Account objects, grouped by
network
v2\Response\SocialServerResponse
Abstract base incl. version check and the fromJson() factory
Response containing all connected accounts, grouped by network
v2\Response\SocialServerErrorResponse
Error response (code with prefix 5550, message)
v2\Response\SocialServerRateLimitResponse
Answer to a throttled request: which limit was hit and from when the
service can be used again
v2\Response\SocialServerVersionMismatchResponse
Answer whose protocol version does not fit the expected one
v2\Response\SocialServerUnrecognizedResponse
Answer whose type is unknown, incl. the received type and the raw
data
Installation
The extension is installed via Composer:
composer require fixpunkt/fp-social-bridge
Copied!
Requirements
Component
Version
TYPO3
12.4 – 14.3
PHP
8.4 – 8.5
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.
The server serializes the object via toArray() and json_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. If the answer does not fit, the factory builds the fitting response
itself:
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:
useFixpunkt\FpSocialBridge\v2\Data\Post;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
useFixpunkt\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());
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
useFixpunkt\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
}
}
Copied!
Note
If a post contains mentions, the mentions field looks like this:
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:
useFixpunkt\FpSocialBridge\v2\Data\Post;
useFixpunkt\FpSocialBridge\v2\Data\Posts;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
useFixpunkt\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());
Copied!
The resulting JSON (posts and nextPage shortened):
Client side – evaluate and iterate over the collection:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
useFixpunkt\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();
}
Copied!
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:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerErrorResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = new SocialServerErrorResponse(
SocialServerResponse::version,
code: 42,
message: 'The requested account does not exist.',
);
echo json_encode($response->toArray());
Copied!
The resulting JSON:
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerErrorResponse",
"version": 2,
"code": 555042,
"message": "The requested account does not exist."
}
Copied!
Note
The supplied error code is combined with the prefix 5550 in the
constructor and stored as an int. So code: 42 becomes 555042.
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.
useFixpunkt\FpSocialBridge\v2\Response\SocialServerAccountsResponse;
useFixpunkt\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();
}
}
}
}
Copied!
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.
Example 5: A reached request limit (SocialServerRateLimitResponse)
The social server limits how often the API may be called: requests to the same
social media profile and the overall number of requests are each capped per API
user. Once a cap is reached the server answers with HTTP 429, a
Retry-After header and a
SocialServerRateLimitResponse.
Server side – create and output as JSON:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerRateLimitResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = new SocialServerRateLimitResponse(
SocialServerResponse::version,
message: 'Das Anfragelimit für dieses Social-Media-Profil ist erreicht '
. '(max. 10 Anfragen pro 1 minute). Weitere Anfragen sind ab '
. '02.09.2026 14:23:45 möglich, also in 37 Sekunden.',
retryAfter: 1788351825,
retryAfterSeconds: 37,
limit: 10,
interval: '1 minute',
scope: SocialServerRateLimitResponse::scopeProfile,
);
echo json_encode($response->toArray());
Copied!
The resulting JSON:
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerRateLimitResponse",
"version": 2,
"code": 55501788307200,
"message": "Das Anfragelimit für dieses Social-Media-Profil ist erreicht (max. 10 Anfragen pro 1 minute). Weitere Anfragen sind ab 02.09.2026 14:23:45 möglich, also in 37 Sekunden.",
"retryAfter": 1788351825,
"retryAfterSeconds": 37,
"limit": 10,
"interval": "1 minute",
"scope": "profile"
}
Copied!
Client side – wait instead of giving up:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerRateLimitResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerRateLimitResponse) {
// Short blocks can be waited out, longer ones belong in the next run.if ($response->getRetryAfterSeconds() <= 10) {
sleep($response->getRetryAfterSeconds() + 1);
// ... retry the request
}
// Otherwise remember the point in time and try again later.
$retryAt = $response->getRetryAfter();
}
Copied!
Warning
SocialServerRateLimitResponseextendsSocialServerErrorResponse. An
instanceof SocialServerErrorResponse check therefore matches it as well,
which is intentional: clients that do not know the type yet still see a
readable error. If you want to handle a throttled request differently, check
for SocialServerRateLimitResponsefirst.
Example 6: An answer that cannot be used (version and unknown type)
Two cases are not down to the content of the answer but to the answer itself not
fitting: its protocol version differs from the expected one, or its type is
unknown. For both, fromJson() returns a response object instead of throwing
-- so a single error branch on the client side is enough.
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerUnrecognizedResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerVersionMismatchResponse;
// Answer of a server that already speaks version 3
$response = SocialServerResponse::fromJson($json);
if ($response instanceof SocialServerVersionMismatchResponse) {
// "Version of answer (3) does not fit request version (2)."
$logger->warning($response->getMessage(), [
'expected' => $response->getExpectedVersion(),
'received' => $response->getReceivedVersion(),
]);
}
if ($response instanceof SocialServerUnrecognizedResponse) {
// "The received response is not recognized: \"...\SocialServerStoryResponse\"."
$logger->warning($response->getMessage(), [
'type' => $response->getReceivedType(),
'payload' => $response->getPayload(),
]);
}
Copied!
The server can send both types itself as well, e.g. if it recognizes the version
of a request as unsupported:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerVersionMismatchResponse;
$response = new SocialServerVersionMismatchResponse(
version: 3,
message: 'Version of answer (3) does not fit request version (2).',
expectedVersion: SocialServerResponse::version,
);
echo json_encode($response->toArray());
Copied!
The resulting JSON:
{
"type": "Fixpunkt\\FpSocialBridge\\v2\\Response\\SocialServerVersionMismatchResponse",
"version": 3,
"code": 55501652117309,
"message": "Version of answer (3) does not fit request version (2).",
"expectedVersion": 2
}
Copied!
Note
Both types are checked before the version check, just like
SocialServerRateLimitResponse: an
answer that reports a version problem has to be understood even when the
versions are exactly what is diverging.
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:
useFixpunkt\FpSocialBridge\v2\Response\SocialServerResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostsResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerPostResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerAccountsResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerErrorResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerRateLimitResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerUnrecognizedResponse;
useFixpunkt\FpSocialBridge\v2\Response\SocialServerVersionMismatchResponse;
$response = SocialServerResponse::fromJson($json);
// Before the error check: these three are subclasses of it.if ($response instanceof SocialServerRateLimitResponse) {
// ... wait or postpone, see above
}
if (
$response instanceof SocialServerVersionMismatchResponse
|| $response instanceof SocialServerUnrecognizedResponse
) {
// ... log the answer, see above
}
if ($response instanceof SocialServerErrorResponse) {
thrownew \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) {
// ...
}
}
Copied!
Note
fromJson() only throws an \Exception if the data is corrupted (code
1684785549) -- a mismatching version and an unknown type each come back
as a response object. See SocialServerResponse.
Class reference
All classes live in the Fixpunkt\FpSocialBridge namespace. They fall into
three types:
Interface – the shared serialization contract.
DTOs (data transfer objects) – the plain data carriers.
Responses – the typed response objects of the social server.
Shared contract of all data-transfer and response objects.
Method
Description
fromJson(string $json): static
Creates an object from a JSON string.
fromArray(array $data): static
Creates an object from an associative array.
toArray(): array
Serializes the object back into an array.
DTOs
The data transfer objects in the v2\Data namespace contain only data and
accessor methods, no business logic.
v2\Data\Post
Represents a single post.
Method
Return value
getId()
string – unique ID of the post
getHeadline()
string – headline (empty for many sources)
getMessage()
string – message text as HTML (may contain <br />/<a> as
well as emoji placeholders {emoji:…})
getPostUrl()
string – URL of the post on the network
getLink()
string – linked URL
getUpdateTime()
\DateTime – time of last update (in JSON as date /
timezone_type / timezone)
getHashtags()
array – hashtags as strings without a leading #
getMentions()
array – mentions as objects with displayName and
systemName
getPictures()
array – picture URLs
v2\Data\Posts
Iterable, countable collection of Post objects. Implements Iterator and
Countable, so it can be used directly with foreach and count().
v2\Data\Channel
A selectable page or channel of a connected account. This is exactly what the
client side writes into the channel and label fields of an account
record.
Method
Return value
getId()
string – id of the page/channel in the network
getName()
string – display name of the page/channel
v2\Data\Account
A single connected account of the social server.
Method
Return value
getUid()
int – uid of the access key record on the social server
getNetwork()
string – connector class name, e.g.
Fixpunkt\FpSocialServer\Networks\Facebook\Connector
getNetworkKey()
string – short form of the network (facebook,
instagram, instagramlogin, linkedin)
getNetworkName()
string – human readable network name, e.g.
Instagram (Direktanmeldung)
getDisplayName()
string – name of the account as shown to the user
getUsername()
string – identifier of the account within the network
getEmail()
string – e-mail address, may be empty
getExpires()
\DateTime|null – expiry of the access key, null for
networks whose keys do not expire
getExpired()
bool – whether the access key has already expired
getChannels()
Channel[] – the pages/channels reachable with this account
v2\Data\Accounts
Iterable, countable collection of Account objects, grouped by network.
Unlike Posts it is not a flat list: iterating yields
the network class name as key and an Account[] as value.
Method
Description
getIterator()
Iterates network => Account[] (IteratorAggregate)
count()
Total number of accounts across all networks
getNetworks()
string[] – the networks that have accounts
getByNetwork(string $network)
Account[] – the accounts of one network, empty if unknown
getAll()
Account[] – all accounts as a flat list
Responses
The response objects in the v2\Response namespace represent the various
response types of the social server. The base class factory turns a received
JSON response into the matching object.
Response containing multiple posts including pagination.
Method
Description
getPosts(): Posts
Collection of the contained posts.
getNext(): string
Cursor for the next page (nextPage).
getPrevious(): string
Cursor for the previous page (previousPage).
v2\Response\SocialServerAccountsResponse
Response containing all accounts the authenticated user has connected on the
social server, grouped by network.
getAccounts(): Accounts – the contained collection.
v2\Response\SocialServerErrorResponse
Error response of the social server.
Method
Description
getCode(): int
Composite error code (prefix 5550).
getMessage(): string
Error message.
v2\Response\SocialServerRateLimitResponse
Response of the social server when a request limit has been reached. Extends
SocialServerErrorResponse, so getCode() and
getMessage() are available as well – getCode() always returns
55501788307200, and getMessage() already spells out the point in time from
which the service can be used again.
Method
Description
getRetryAfter(): \DateTimeImmutable
Point in time from which requests are allowed again, in the local
time zone.
getRetryAfterTimestamp(): int
The same point in time as a unix timestamp.
getRetryAfterSeconds(): int
Seconds until then. Suitable for a Retry-After header or a wait
before retrying.
getLimit(): int
Number of requests allowed within the interval.
getInterval(): string
The interval of the limit, e.g. 1 minute.
getScope(): string
Which limit was hit: profile for requests to the same social
media profile, general for the overall limit.
v2\Response\SocialServerVersionMismatchResponse
Response whose protocol version does not fit the expected one. Extends
SocialServerErrorResponse; getCode() always
returns 55501652117309 and getMessage() names both versions.
Method
Description
getExpectedVersion(): int
Protocol version that was expected (currently 2).
getReceivedVersion(): int
Protocol version the answer was delivered with. Identical to
getVersion().
v2\Response\SocialServerUnrecognizedResponse
Response whose type is unknown and that carries neither code nor message.
Extends SocialServerErrorResponse;
getCode() always returns 55501741293955.
Method
Description
getReceivedType(): string
The type the answer stated, or an empty string if it did not carry
one.
getPayload(): array
Raw data of the answer, e.g. for the log.
Changelog
1.5.0
Added v2\Response\SocialServerRateLimitResponse. The social server answers with it
when a request limit has been reached, and it carries the point in time from which the
service can be used again -- as a unix timestamp (retryAfter), as the remaining
seconds (retryAfterSeconds) and spelled out in message. limit and interval
name the limit that was hit, scope says whether it was the per-profile limit
(profile) or the overall one (general).
The new response extends SocialServerErrorResponse. Clients that do not know the type
yet therefore still treat a throttled request as an ordinary error with a readable
message instead of failing on a type error.
SocialServerResponse::fromJson() now recognizes the new rate limit response, and it
reads an unknown response type that carries code and message as an error response
instead of throwing. Newer server versions no longer break older clients hard.
Added v2\Response\SocialServerVersionMismatchResponse. It replaces the exception with
code 1652117309: if the protocol version of the answer does not match the expected one,
fromJson() returns this response instead of throwing. getExpectedVersion() and
getReceivedVersion() name both versions.
Added v2\Response\SocialServerUnrecognizedResponse. It replaces the exception with
code 1741293955: an answer whose type is unknown and that carries neither code nor
message is returned as this response. getReceivedType() and getPayload() keep
the type and the raw data of the answer available for logging.
Both new responses extend SocialServerErrorResponse as well, so an existing
instanceof SocialServerErrorResponse branch already covers them with a readable
message. fromJson() therefore only throws for corrupted data (code 1684785549)
now; code that catches the two former exceptions can be dropped.
1.4.0
Added v2\Response\SocialServerAccountsResponse together with the data
transfer objects v2\Data\Account, v2\Data\Accounts and
v2\Data\Channel. They carry the accounts a user has connected on the
social server, grouped by network, so that the client side can prefill the
account, channel and label fields of an account record.
SocialServerResponse::fromJson() now also recognizes the new accounts
response.
1.3.0
Added compatibility with TYPO3 14. The extension now supports TYPO3 12.4,
13.4 and 14.3.
Reference to the headline
Copy and freely share the link
This link target has no permanent anchor assigned.The link below can be used, but is prone to change if the page gets moved.