Skip to main content

Server API walkthrough

Server API provides different methods to interact with Centrifugo. Specifically, in most cases this is an entry point for publications into channels coming from your application backend. There are two kinds of server API available at the moment:

  • HTTP API
  • GRPC API

Both are similar in terms of request/response structures.

HTTP API

Server HTTP API works on /api path prefix (by default). The request format is super-simple: this is an HTTP POST request to a specific method API path with application/json Content-Type, X-API-Key header and with JSON body.

Instead of many words, here is an example how to call publish method:

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "test", "data": {"value": "test_value"}}' \
http://localhost:8000/api/publish
tip

You can just use one of our available HTTP API libraries or use Centrifugo GRPC API to avoid manually constructing requests structures.

Below we look at all aspects of HTTP API in detail, starting with information about authorization.

HTTP API authorization

HTTP API is protected by api_key set in Centrifugo configuration. I.e. api_key option must be added to config, like:

config.json
{
...
"api_key": "<YOUR_API_KEY>"
}

This API key must be set in the request X-API-Key header in this way:

X-API-Key: <YOUR_API_KEY>

It's also possible to pass API key over URL query param. Simply add ?api_key=<YOUR_API_KEY> query param to the API endpoint. Keep in mind that passing the API key in the X-API-Key header is a recommended way as it is considered more secure.

To disable API key check on Centrifugo side you can use api_insecure configuration option. Use it in development only or make sure to protect the API endpoint by proxy or firewall rules in production – to prevent anyone with access to the endpoint to send commands over your unprotected Centrifugo API.

API key auth is not very safe for man-in-the-middle so we also recommended protecting Centrifugo with TLS.

API methods

Server API supports many methods. Let's describe them starting with the most important publish operation.

publish

Publish method allows publishing data into a channel (we call this message publication in Centrifugo). Most probably this is a command you'll use most of the time.

Here is an example of publishing message to Centrifugo:

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "chat", "data": {"text": "hello"}}' \
http://localhost:8000/api/publish

In case of successful publish you will get a response like this:

{
"result": {}
}

As an additional example, let's take a look how to publish to Centrifugo with requests library for Python:

import json
import requests

api_key = "YOUR_API_KEY"
data = json.dumps({
"channel": "docs",
"data": {
"content": "1"
}
})
headers = {'Content-type': 'application/json', 'X-API-Key': api_key}
resp = requests.post("https://centrifuge.example.com/api/publish", data=data, headers=headers)
print(resp.json())

In case of publication error, response object will contain error field. For example, let's publish to an unknown namespace (not defined in Centrifugo configuration):

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "unknown:chat", "data": {"text": "hello"}}' \
http://localhost:8000/api/publish

In response you will also get 200 OK, but payload will contain error field instead of result:

{
"error": {
"code": 102,
"message": "namespace not found"
}
}

error object contains error code and message - this is also the same for other commands described below.

Publish request

Field nameField typeRequiredDescription
channelstringyesName of channel to publish
dataany JSONyesCustom JSON data to publish into a channel
skip_historyboolnoSkip adding publication to history for this request
tagsmap[string]stringnoPublication tags - map with arbitrary string keys and values which is attached to publication and will be delivered to clients
b64datastringnoCustom binary data to publish into a channel encoded to base64 so it's possible to use HTTP API to send binary to clients. Centrifugo will decode it from base64 before publishing. In case of GRPC you can publish binary using data field.
idempotency_keystringnoOptional idempotency key to drop duplicate publications upon retries. It acts per channel. Centrifugo currently keeps the cache of idempotent publish results during 5 minutes window. Available since Centrifugo v5.2.0

Publish result

Field nameField typeOptionalDescription
offsetintegeryesOffset of publication in history stream
epochstringyesEpoch of current stream

broadcast

broadcast is similar to publish but allows to efficiently send the same data into many channels:

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channels": ["user:1", "user:2"], "data": {"text": "hello"}}' \
http://localhost:8000/api/broadcast

Broadcast request

Field nameField typeRequiredDescription
channelsArray of stringsyesList of channels to publish data to
dataany JSONyesCustom JSON data to publish into each channel
skip_historyboolnoSkip adding publications to channels' history for this request
tagsmap[string]stringnoPublication tags - map with arbitrary string keys and values which is attached to publication and will be delivered to clients
b64datastringnoCustom binary data to publish into a channel encoded to base64 so it's possible to use HTTP API to send binary to clients. Centrifugo will decode it from base64 before publishing. In case of GRPC you can publish binary using data field.
idempotency_keystringnoOptional idempotency key to drop duplicate publications upon retries. It acts per channel. Centrifugo currently keeps the cache of idempotent publish results during 5 minutes window. Available since Centrifugo v5.2.0

Broadcast result

Field nameField typeOptionalDescription
responsesArray of publish responsesnoResponses for each individual publish (with possible error and publish result)

subscribe

subscribe allows subscribing active user's sessions to a channel. Note, it's mostly for dynamic server-side subscriptions.

Subscribe request

Field nameField typeRequiredDescription
userstringyesUser ID to subscribe
channelstringyesName of channel to subscribe user to
infoany JSONnoAttach custom data to subscription (will be used in presence and join/leave messages)
b64infostringnoinfo in base64 for binary mode (will be decoded by Centrifugo)
clientstringnoSpecific client ID to subscribe (user still required to be set, will ignore other user connections with different client IDs)
sessionstringnoSpecific client session to subscribe (user still required to be set)
dataany JSONnoCustom subscription data (will be sent to client in Subscribe push)
b64datastringnoSame as data but in base64 format (will be decoded by Centrifugo)
recover_sinceStreamPosition objectnoStream position to recover from
overrideOverride objectnoAllows dynamically override some channel options defined in Centrifugo configuration (see below available fields)

Override object

FieldTypeOptionalDescription
presenceBoolValueyesOverride presence
join_leaveBoolValueyesOverride join_leave
force_push_join_leaveBoolValueyesOverride force_push_join_leave
force_positioningBoolValueyesOverride force_positioning
force_recoveryBoolValueyesOverride force_recovery

BoolValue is an object like this:

{
"value": true/false
}

Subscribe result

Empty object at the moment.

unsubscribe

unsubscribe allows unsubscribing user from a channel.

Unsubscribe request

Field nameField typeRequiredDescription
userstringyesUser ID to unsubscribe
channelstringyesName of channel to unsubscribe user to
clientstringnoSpecific client ID to unsubscribe (user still required to be set)
sessionstringnoSpecific client session to disconnect (user still required to be set).

Unsubscribe result

Empty object at the moment.

disconnect

disconnect allows disconnecting a user by ID.

Disconnect request

Field nameField typeRequiredDescription
userstringyesUser ID to disconnect
clientstringnoSpecific client ID to disconnect (user still required to be set)
sessionstringnoSpecific client session to disconnect (user still required to be set).
whitelistArray of stringsnoArray of client IDs to keep
disconnectDisconnect objectnoProvide custom disconnect object, see below

Disconnect object

Field nameField typeRequiredDescription
codeintyesDisconnect code
reasonstringyesDisconnect reason

Disconnect result

Empty object at the moment.

refresh

refresh allows refreshing user connection (mostly useful when unidirectional transports are used).

Refresh request

Field nameField typeRequiredDescription
userstringyesUser ID to refresh
clientstringnoClient ID to refresh (user still required to be set)
sessionstringnoSpecific client session to refresh (user still required to be set).
expiredboolnoMark connection as expired and close with Disconnect Expired reason
expire_atintnoUnix time (in seconds) in the future when the connection will expire

Refresh result

Empty object at the moment.

presence

presence allows getting channel online presence information (all clients currently subscribed on this channel).

tip

Presence in channels is not enabled by default. See how to enable it over channel options. Also check out dedicated chapter about it.

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "chat"}' \
http://localhost:8000/api/presence

Example response:

{
"result": {
"presence": {
"c54313b2-0442-499a-a70c-051f8588020f": {
"client": "c54313b2-0442-499a-a70c-051f8588020f",
"user": "42"
},
"adad13b1-0442-499a-a70c-051f858802da": {
"client": "adad13b1-0442-499a-a70c-051f858802da",
"user": "42"
}
}
}
}

Presence request

Field nameField typeRequiredDescription
channelstringyesName of channel to call presence from

Presence result

Field nameField typeOptionalDescription
presenceMap of client ID (string) to ClientInfo objectnoOffset of publication in history stream

ClientInfo

Field nameField typeOptionalDescription
clientstringnoClient ID
userstringnoUser ID
conn_infoJSONyesOptional connection info
chan_infoJSONyesOptional channel info

presence_stats

presence_stats allows getting short channel presence information - number of clients and number of unique users (based on user ID).

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "chat"}' \
http://localhost:8000/api/presence_stats

Example response:

{
"result": {
"num_clients": 0,
"num_users": 0
}
}

Presence stats request

Field nameField typeRequiredDescription
channelstringyesName of channel to call presence from

Presence stats result

Field nameField typeOptionalDescription
num_clientsintegernoTotal number of clients in channel
num_usersintegernoTotal number of unique users in channel

history

history allows getting channel history information (list of last messages published into the channel). By default if no limit parameter set in request history call will only return current stream position information - i.e. offset and epoch fields. To get publications you must explicitly provide limit parameter. See also history API description in special doc chapter.

tip

History in channels is not enabled by default. See how to enable it over channel options.

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"channel": "chat", "limit": 2}' \
http://localhost:8000/api/history

Example response:

{
"result": {
"epoch": "qFhv",
"offset": 4,
"publications": [
{
"data": {
"text": "hello"
},
"offset": 2
},
{
"data": {
"text": "hello"
},
"offset": 3
}
]
}
}

History request

Field nameField typeRequiredDescription
channelstringyesName of channel to call history from
limitintnoLimit number of returned publications, if not set in request then only current stream position information will present in result (without any publications)
sinceStreamPosition objectnoTo return publications after this position
reverseboolnoIterate in reversed order (from latest to earliest)

StreamPosition

Field nameField typeRequiredDescription
offsetintegeryesOffset in a stream
epochstringyesStream epoch

History result

Field nameField typeOptionalDescription
publicationsArray of publication objectsyesList of publications in channel
offsetintegeryesTop offset in history stream
epochstringyesEpoch of current stream

history_remove

history_remove allows removing publications in channel history. Current top stream position meta data kept untouched to avoid client disconnects due to insufficient state.

History remove request

Field nameField typeRequiredDescription
channelstringyesName of channel to remove history

History remove result

Empty object at the moment.

channels

channels return active channels (with one or more active subscribers in it).

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{}' \
http://localhost:8000/api/channels

Channels request

Field nameField typeRequiredDescription
patternstringnoPattern to filter channels, we are using gobwas/glob library for matching

Channels result

Field nameField typeOptionalDescription
channelsMap of string to ChannelInfonoMap where key is channel and value is ChannelInfo (see below)

ChannelInfo

Field nameField typeOptionalDescription
num_clientsintegernoTotal number of connections currently subscribed to a channel
caution

Keep in mind that since the channels method by default returns all active channels it can be really heavy for massive deployments. Centrifugo does not provide a way to paginate over channels list. At the moment we mostly suppose that channels API call will be used in the development process or for administrative/debug purposes, and in not very massive Centrifugo setups (with no more than 10k active channels). A better and scalable approach for huge setups could be a real-time analytics approach described here.

info

info method allows getting information about running Centrifugo nodes.

Example response:

{
"result": {
"nodes": [
{
"name": "Alexanders-MacBook-Pro.local_8000",
"num_channels": 0,
"num_clients": 0,
"num_users": 0,
"uid": "f844a2ed-5edf-4815-b83c-271974003db9",
"uptime": 0,
"version": ""
}
]
}
}

Info request

Empty object at the moment.

Info result

Field nameField typeOptionalDescription
nodesArray of Node objectsnoInformation about all nodes in a cluster

batch

Batch allows sending many commands in one request. Commands processed sequentially by Centrifugo, users should check individual error in each returned reply. Useful to avoid RTT latency penalty for each command sent, this is an analogue of pipelining.

Example with two publications in one request:

curl --header "X-API-Key: <API_KEY>" \
--request POST \
--data '{"commands": [{"publish": {"channel": "test1", "data": {}}}, {"publish": {"channel": "x:test2", "data": {}}}]}' \
http://localhost:8000/api/batch

Example response:

{
"replies":[
{"publish":{}},
{"error":{"code":102,"message":"unknown channel"}}
]
}

Starting from Centrifugo v5.2.0 it's also possible to pass "parallel": true on batch data top level to make batch commands processing parallel on Centrifugo side. This may provide reduced latency (especially in case of using Redis engine).

HTTP API libraries

Sending an API request to Centrifugo is a simple task to do in any programming language - this is just a POST request with JSON payload in body and Authorization header.

But we have several official HTTP API libraries for different languages, to help developers to avoid constructing proper HTTP requests manually:

Also, there are API libraries created by community:

tip

Also, keep in mind that Centrifugo has GRPC API so you can automatically generate client API code for your language.

GRPC API

Centrifugo also supports GRPC API. With GRPC it's possible to communicate with Centrifugo using a more compact binary representation of commands and use the power of HTTP/2 which is the transport behind GRPC.

GRPC API is also useful if you want to publish binary data to Centrifugo channels.

tip

GRPC API allows calling all commands described in HTTP API doc, actually both GRPC and HTTP API in Centrifugo based on the same Protobuf schema definition. So refer to the HTTP API description doc for the parameter and the result field description.

You can enable GRPC API in Centrifugo using grpc_api option:

config.json
{
...
"grpc_api": true
}

By default, GRPC will be served on port 10000 but you can change it using the grpc_api_port option.

Now, as soon as Centrifugo started – you can send GRPC commands to it. To do this get our API Protocol Buffer definitions from this file.

Then see GRPC docs specific to your language to find out how to generate client code from definitions and use generated code to communicate with Centrifugo.

GRPC example for Python

For example for Python you need to run sth like this according to GRPC docs:

pip install grpcio-tools
python -m grpc_tools.protoc -I ./ --python_out=. --grpc_python_out=. api.proto

As soon as you run the command you will have 2 generated files: api_pb2.py and api_pb2_grpc.py. Now all you need is to write a simple program that uses generated code and sends GRPC requests to Centrifugo:

import grpc
import api_pb2_grpc as api_grpc
import api_pb2 as api_pb

channel = grpc.insecure_channel('localhost:10000')
stub = api_grpc.CentrifugoApiStub(channel)

try:
resp = stub.Info(api_pb.InfoRequest())
except grpc.RpcError as err:
# GRPC level error.
print(err.code(), err.details())
else:
if resp.error.code:
# Centrifugo server level error.
print(resp.error.code, resp.error.message)
else:
print(resp.result)

Note that you need to explicitly handle Centrifugo API level error which is not transformed automatically into GRPC protocol-level error.

GRPC example for Go

Here is a simple example of how to run Centrifugo with the GRPC Go client.

You need protoc, protoc-gen-go and protoc-gen-go-grpc installed.

First start Centrifugo itself with GRPC API enabled:

CENTRIFUGO_GRPC_API=1 centrifugo --config config.json

In another terminal tab:

mkdir centrifugo_grpc_example
cd centrifugo_grpc_example/
touch main.go
go mod init centrifugo_example
mkdir apiproto
cd apiproto
wget https://raw.githubusercontent.com/centrifugal/centrifugo/master/internal/apiproto/api.proto -O api.proto

Run protoc to generate code:

protoc -I ./ api.proto --go_out=. --go-grpc_out=.

Put the following code to main.go file (created on the last step above):

package main

import (
"context"
"log"
"time"

"centrifugo_example/apiproto"

"google.golang.org/grpc"
)

func main() {
conn, err := grpc.Dial("localhost:10000", grpc.WithInsecure())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
client := apiproto.NewCentrifugoApiClient(conn)
for {
resp, err := client.Publish(context.Background(), &apiproto.PublishRequest{
Channel: "chat:index",
Data: []byte(`{"input": "hello from GRPC"}`),
})
if err != nil {
log.Printf("Transport level error: %v", err)
} else {
if resp.GetError() != nil {
respError := resp.GetError()
log.Printf("Error %d (%s)", respError.Code, respError.Message)
} else {
log.Println("Successfully published")
}
}
time.Sleep(time.Second)
}
}

Then run:

go run main.go

The program starts and periodically publishes the same payload into chat:index channel.

GRPC API key authorization

You can also set grpc_api_key (string) in Centrifugo configuration to protect GRPC API with key. In this case, you should set per RPC metadata with key authorization and value apikey <KEY>. For example in Go language:

package main

import (
"context"
"log"
"time"

"centrifugo_example/apiproto"

"google.golang.org/grpc"
)

type keyAuth struct {
key string
}

func (t keyAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"authorization": "apikey " + t.key,
}, nil
}

func (t keyAuth) RequireTransportSecurity() bool {
return false
}

func main() {
conn, err := grpc.Dial("localhost:10000", grpc.WithInsecure(), grpc.WithPerRPCCredentials(keyAuth{"xxx"}))
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
client := apiproto.NewCentrifugoClient(conn)
for {
resp, err := client.Publish(context.Background(), &PublishRequest{
Channel: "chat:index",
Data: []byte(`{"input": "hello from GRPC"}`),
})
if err != nil {
log.Printf("Transport level error: %v", err)
} else {
if resp.GetError() != nil {
respError := resp.GetError()
log.Printf("Error %d (%s)", respError.Code, respError.Message)
} else {
log.Println("Successfully published")
}
}
time.Sleep(time.Second)
}
}

For other languages refer to GRPC docs.

Transport error mode

By default, Centrifugo server API never returns transport level errors - for example it always returns 200 OK for HTTP API and never returns GRPC transport-level errors. Centrifugo returns its custom errors from API calls inside optional error field of response as we showed above in this doc. This means that API call to Centrifigo API may returns 200 OK, but in the error field you may find Centrifugo-specific 100: internal error.

Since Centrifugo v5.1.0 Centrifigo has an option to use transport-native error codes instead of Centrifugo error field in the response. The main motivation is make API calls friendly to integrate with the network ecosystem - for automatic retries, better logging, etc. In many situations this may be more obvious for humans also.

Let's show an example. Without any special options HTTP request to Centrifigo server API which contains error in response looks like this:

❯ echo '{}' | http POST "http://localhost:8000/api/publish"
HTTP/1.1 200 OK
Content-Length: 46
Content-Type: application/json
Date: Sat, 19 Aug 2023 07:23:40 GMT

{
"error": {
"code": 107,
"message": "bad request"
}
}

Note - it returns 200 OK even though response contains error field. With transport error mode request-response may be transformed into the following:

❯ echo '{}' | http POST "http://localhost:8000/api/publish" "X-Centrifugo-Error-Mode: transport"
HTTP/1.1 400 Bad Request
Content-Length: 36
Content-Type: application/json
Date: Sat, 19 Aug 2023 07:23:59 GMT

{
"code": 107,
"message": "bad request"
}

Transport error mode may be turned on globally:

  • using "api_error_mode": "transport" option for HTTP server API
  • using "grpc_api_error_mode": "transport" option for GRPC server API

Also, this mode may be used on per-request basis:

  • by setting custom header X-Centrifugo-Error-Mode: transport for HTTP (as we just showed in the example)
  • adding custom metadata key x-centrifugo-error-mode: transport for GRPC
caution

Note, that transport error mode does not help a lot with Batch and Broadcast APIs which are quite special because these calls contain many independent operations. For these calls you still need to look at individual error objects in response.

To achieve the goal we have an internal matching of Centrifugo API error codes to HTTP and GRPC error codes.

Centrifugo error code to HTTP code

func MapErrorToHTTPCode(err *Error) int {
switch err.Code {
case ErrorInternal.Code: // 100 -> HTTP 500
return http.StatusInternalServerError
case ErrorUnknownChannel.Code, ErrorNotFound.Code: // 102, 104 -> HTTP 404
return http.StatusNotFound
case ErrorBadRequest.Code, ErrorNotAvailable.Code: // 107, 108 -> HTTP 400
return http.StatusBadRequest
case ErrorUnrecoverablePosition.Code: // 112 -> HTTP 416
return http.StatusRequestedRangeNotSatisfiable
case ErrorConflict.Code: // 113 -> HTTP 409
return http.StatusConflict
default:
// Default to Internal Error for unmapped errors.
// In general should be avoided - all new API errors must be explicitly described here.
return http.StatusInternalServerError // HTTP 500
}
}

Centrifugo error code to GRPC code

func MapErrorToGRPCCode(err *Error) codes.Code {
switch err.Code {
case ErrorInternal.Code: // 100
return codes.Internal
case ErrorUnknownChannel.Code, ErrorNotFound.Code: // 102, 104
return codes.NotFound
case ErrorBadRequest.Code, ErrorNotAvailable.Code: // 107, 108
return codes.InvalidArgument
case ErrorUnrecoverablePosition.Code: // 112
return codes.OutOfRange
case ErrorConflict.Code: // 113
return codes.AlreadyExists
default:
// Default to Internal Error for unmapped errors.
// In general should be avoided - all new API errors must be explicitly described here.
return codes.Internal
}
}