Please be aware that you are viewing our bleeding edge unstable documentation. Unless you wanted to view the bleeding edge (and possibly unstable) documentation, we recommend you use our stable docs.

Go to Ably's stable canonical documentation »

I know what I'm doing, let me see the bleeding edge docs »

You are viewing our bleeding edge unstable documentation. We recommend you use our stable documentation »
Fork me on GitHub

Features spec v0.8

A detailed test specification that applies to all client libraries is generated from the Ably Ruby client library’s acceptance and test suites. Whilst every official Ably client library has test coverage, the amount of test coverage varies, and as such our recommendation is to refer to the official test specification when developing a client library.

However, we have found the test specification can be difficult as a reference because of both its breadth and the fact that it applies to the Ruby client library which may be unfamiliar as a language for a lot of developers.

As a result, this document outlines the complete feature set of both the REST and Realtime client libraries. It is expected that every client library developer refers to this document to ensure that their client library provides the same API and features as the existing Ably client libraries. In addition to this, it is essential that there is test coverage over all of the features described below.

We recommend you use the IDL and the Ably Java library as a reference when reviewing how the API has been implemented.

Test guidelines

REST client library

Client library developers – clone our REST client library Google Doc spec when developing a REST client library to help you keep track of feature compliance and test coverage.







Realtime client library features

The Ably Realtime client libraries establish and maintain a persistent connection to Ably and provide methods to publish and subscribe to messages over a low latency realtime connection.

The Realtime library is a superset of the REST library and as such all Realtime libraries provide the functionality available in the REST library in addition to Realtime-specific features.

The threading and/or asynchronous model for each realtime library will vary by language and it is therefore up to the developer to decide on the best approach for each given client library. For example, Node.js and Ruby (EventMachine) use a similar callback single threaded evented approach that ensures all public methods are non-blocking. Java and .NET use a threaded model whereby the Connection runs in its own thread. Go makes extensive use of goroutines and channels.






EventEmitter mixin / interface

State conditions and operations

Connection.state effects on realtime operations

Initialized Connecting Connected Disconnected Suspended Closing Closed Failed
connect RTN11a No-op No-op RTN11c RTN11c RTN11b RTN11a RTN11d
close No-op RTN12f RTN12a RTN12d RTN12d No-op No-op No-op
ping RTN13b RTN13c RTN13a RTN13c RTN13b RTN13b RTN13b RTN13b
Channel attach RTL4h RTL4h See channel states table RTL4h RTL4b RTL4b RTL4b RTL4b
Channel detach RTL5h RTL5h See channel states table RTL5h See channel states table RTL5g See channel states table RTL5g
Channel publish RTL6c2 RTL6c2 See channel states table RTL6c2 RTL6c4 RTL6c4 RTL6c4 RTL6c4
Presence ops. RTP16b RTP16b See channel states table RTP16b RTP16c RTP16c RTP16c RTP16c

RealtimeChannel.state effects on channel operations

Initialized Attaching Attached Detaching Detached Failed
attach RTL4c RTL4h RTL4a RTL4h RTL4c RTL4g
detach RTL5h RTL5i RTL5d RTL5i RTL5a RTL5b
publish RTL6c2 RTL6c2 RTL6c1 RTL6c4 RTL6c4 RTL6c3
Presence ops. RTP16b RTP16b RTP16a RTP16c RTP16c RTP16c


Data types










Capability – API not defined yet


Option types






Client Library defaults

The following default values are configured for the client library:

Interface Definition

The following bespoke IDL (Interface Definition Language) describes the types and classes present in the Rest and Realtime client libraries.

Please note the following conventions:

class Rest:
  constructor(keyStr: String) // RSC1
  constructor(tokenStr: String) // RSC1
  constructor(ClientOptions) // RSC1
  auth: Auth // RSC5
  channels: Channels<RestChannel> // RSN1
    start: Time, // RSC6b1
    end: Time api-default now(), // RSC6b1
    direction: .Backwards | .Forwards api-default .Backwards, // RSC6b2
    limit: int api-default 100, // RSC6b3
    unit: .Minute | .Hour | .Day | .Month api-default .Minute // RSC6b4
  ) => io PaginatedResult<Stats> // RSC6a
  time() => io Time // RSC16

class Realtime:
  constructor(keyStr: String) // RSC1
  constructor(tokenStr: String) // RSC1
  constructor(ClientOptions) // RSC1
  auth: Auth // RTC4
  channels: Channels<RealtimeChannel> // RTC3, RTS1
  clientId: String? // proxy for RSA7
  connection: Connection // RTC2
  stats: // Same as Rest.stats, RTC5a
  close() // proxy for RTN12
  connect() // proxy for RTN11
  time() => io Time // RTC6a

class ClientOptions:
  embeds AuthOptions // TO3j
  autoConnect: Bool default true // RTC1b, TO3e
  clientId: String? // RSC17, RSA4, RSA15, TO3a
  defaultTokenParams: TokenParams? // TO3j11
  echoMessages: Bool default true // RTC1a, TO3h
  environment: String? // RSC15b, TO3k1
  logHandler: // platform specific - TO3c
  logLevel: // platform specific - TO3b
  port: Int default 80 // TO3k4
  queueMessages: Bool default true // RTP16b, TO3g
  restHost: String default "" // RSC12, TO3k2
  realtimeHost: String default "" // RTC1d, TO3k3
  recover: String? // RTC1c, TO3i
  tls: Bool default true // RSC18, TO3d
  tlsPort: Int default 443 // TO3k5
  useBinaryProtocol: Bool default true // TO3f
  // configurable retry and failure defaults
  disconnectedRetryTimeout: Duration default 15s // TO311
  suspendedRetryTimeout: Duration default 30s // RTN14d, TO312
  httpOpenTimeout: Duration default 4s // TO313
  httpRequestTimeout: Duration default 15s // TO314
  httpMaxRetryCount: Int default 3 // TO315
  httpMaxRetryDuration: Duration default 10s // TO315

class AuthOptions: // RSA8e
  authCallback: (String | TokenDetails | TokenRequest) ->? // RSC14c, RSA4, TO3j5, AO2b
  authHeaders: [(String, String)]? // RSA8c3, TO3j8, AO2e
  authMethod: .GET | .POST default .GET // RSA8c, TO3j7, AO2d
  authParams: [String: [String]]? // RSA8c3, RSA8c1, TO3j9, AO2f
  authUrl: String? // RSC14c, RSA4, RSA8c, TO3j6, AO2c
  force: Bool default false // RSA10d, AO2h
  key: String? // RSC14a, RSA14, TO3j1, AO2a
  queryTime: Bool default false // RSA9d, TO3j10, AO2a
  token: String? | TokenDetails? // RSC14c, RSA4, TO3j2
  tokenDetails: TokenDetails? // RSC14c, RSA4, TO3j3
  useTokenAuth: Bool? // RSA4, RSA14, TO3j4

class TokenParams: // RSAA8e
  capability: String api-default '{"*":["*"]}' // RSA9f, TK2b
  clientId: String? // TK2c
  nonce: String? // RSA9c, Tk2d
  timestamp: Time? // RSA9d, Tk2d
  ttl: Duration api-default 60min // RSA9e, TK2a

class Auth:
  clientId: String? // RSA7, RSC17, RSA12
  authorise(TokenParams?, AuthOptions?) => io TokenDetails // RSA10
  createTokenRequest(TokenParams?, AuthOptions?) => io TokenRequest // RSA9
  requestToken(TokenParams?, AuthOptions?) => io TokenDetails // RSA8e

class TokenDetails:
  capability: String // TD5
  clientId: String? // TD6
  expires: Time // TD3
  issued: Time // TD4
  token: String // TD2

class TokenRequest:
  capability: String // TE3
  clientId: String? // TE2
  keyName: String // TE2
  mac: String // TE2
  nonce: String // TE2
  timestamp: Time? // TE5
  ttl: Duration? api-default 60min // TE4

class Channels<ChannelType>:
  exists(String) -> Bool // RSN2, RTS2
  get(String) -> ChannelType // RSN3a, RTS3a
  get(String, ChannelOptions) -> ChannelType // RSN3c, RTS3c
  iterate() -> Iterator<ChannelType> // RSN2, RTS2
  release(String) // RSN4, RTS4

class RestChannel:
  name: String?
  presence: RestPresence // RSL3
    start: Time, // RSL2b1
    end: Time api-default now(), // RSL2b1
    direction: .Backwards | .Forwards api-default .Backwards, // RSL2b2
    limit: int api-default 100 // RSL2b3
  ) => io PaginatedResult<Message> // RSL2a
  publish([Message]) => io // RSL1
  publish(name: String?, data: Data?) => io // RSL1
  publish(name: String?, data: Data?, clientId: String) => io // RSL1h

class RealtimeChannel:
  embeds EventEmitter<ChannelEvent, ErrorInfo?> // RTL2
  errorReason: ErrorInfo? // RTL4e
  state: ChannelState // RTL2b
  presence: RealtimePresence // RTL9
  attach() => io // RTL4d
  detach() => io // RTL5e
    start: Time, // RTL10a
    end: Time api-default now(), // RTL10a
    direction: .Backwards | .Forwards api-default .Backwards, // RTL10a
    limit: int api-default 100, // RTL10a
    untilAttach: Bool default false // RTL10b
  ) => io PaginatedResult<Message> // RSL2a
  publish([Message]) => io // RTL6i
  publish(name: String?, data: Data?) => io // RTL6i
  publish(name: String?, data: Data?, clientId: String) => io // RTL6h
  subscribe((Message) ->) => io // RTL7a
  subscribe(String, (Message) ->) => io // RTL7b
  unsubscribe() // RTL8a, RTE5
  unsubscribe((Message) ->) // RTL8a
  unsubscribe(String, (Message) ->) // RTL8a

enum ChannelState:

enum ChannelEvent:
  embeds ChannelState
  ERROR // RTL2c

class ChannelOptions:
  +withCipherKey(key: Binary | String)? -> ChannelOptions // TB3
  cipher: (CipherParams | Params)? // RSL5a, TB2b

class CipherParams:
  algorithm: String default "AES" // TZ2a
  key: Binary // TZ2d
  keyLength: Int // TZ2b
  mode: String default "CBC" // TZ2c

class Crypto:
  +getDefaultParams(Params) -> CipherParams // RSE1
  +generateRandomKey(keyLength: Int?) => io Binary // RSE2

class RestPresence:
    limit: int api-default 100, // RSP3a
    clientId: String?, // RSP3a2
    connectionId: String?, // RSP3a3
  ) => io PaginatedResult<PresenceMessage> // RSPa
    start: Time, // RSP4b1
    end: Time api-default now(), // RSP4b1
    direction: .Backwards | .Forwards api-default .Backwards, // RSP4b2
    limit: int api-default 100, // RSP4b3
  ) => io PaginatedResult<PresenceMessage> // RSP4a

class RealtimePresence:
  syncComplete: Bool // RTP13
    waitForSync: Bool default true, // RTP11c1
    clientId: String?, // RTP11c2
    connectionId: String?, // RTP11c3
  ) => io [PresenceMessage] // RTP11
    start: Time, // RTP12a
    end: Time, // RTP12a
    direction: .Backwards | .Forwards api-default .Backwards, // RTP12a
    limit: int api-default 100, // RTP12a
    untilAttach: Bool default false // RTP12b
  ) => io PaginatedResult<PresenceMessage> // RTP12c
  subscribe((PresenceMessage) ->) => io // RTP6a
  subscribe(PresenceAction, (PresenceMessage) ->) => io // RTP6b
  unsubscribe() // RTP7a, RTE5
  unsubscribe((PresenceMessage) ->) // RTP7a
  unsubscribe(PresenceAction, (PresenceMessage) ->) // RTP7b
  // presence state modifiers
  enter(Data?) => io // RTP8
  update(Data?) => io // RTP9
  leave(Data?) => io // RTP10
  enterClient(clientId: String, Data?) => io // RTP4, RTP14, RTP15
  updateClient(clientId: String, Data?) => io // RTP15
  leaveClient(clientId: String, Data?) => io // RTP15

enum PresenceAction:
  ENTER // TP2
  LEAVE // TP2

class ConnectionDetails:
  clientId: String? // RSA12a, CD2a
  connectionKey: String // RTN15e, CD2b
  connectionStateTtl: Duration // CD2f, RTN14e, DF1a
  maxFrameSize: Int // CD2d
  maxInboundRate: Int // CD2e
  maxMessageSize: Int // CD2c
  serverId: String // CD2g

class Message:
  constructor(name: String?, data: Data?) // TM2
  constructor(name: String?, data: Data?, clientId: String?) // TM2
  clientId: String? // RSL1g1, TM2b
  connectionId: String? // TM2c
  data: Data? // TM2d
  encoding: String? // TM2e
  id: String // TM2a
  name: String? // TM2g
  timestamp: Time // TM2f

class PresenceMessage
  action: PresenceAction // TP3b
  clientId: String // TP3c
  connectionId: String // TP3d
  data: Data? // TP3e
  encoding: String? // TP3f
  id: String // TP3a
  timestamp: Time // TP3g
  memberKey() -> String // TP3h

class ProtocolMessage:
  action: ProtocolMessageAction // TR2, TR4a
  channel: String? // TR4b
  channelSerial: String? // TR4c
  connectionDetails: ConnectionDetails? // RSA7b3, RTN19, TR4o
  connectionId: String? // RTN15c1, TR4d
  connectionKey: String? // TR4e
  connectionSerial: Int? // RTN10c, TR4f
  count: Int? // TR4g
  error: ErrorInfo? // RTN15c2, TR4h
  flags: .HAS_PRESENCE & .HAS_BACKLOG ? // RTP1, TR3, TR4i
  id: String? // TR4b
  messages: [Message]? // TR4k
  msgSerial: Int? // RTN7b, TR4j
  presence: [PresenceMessage]? // TR4l
  timestamp: Time? // TR4m

enum ProtocolMessageAction:
  ACK // TR2
  NACK // TR2
  CLOSE // TR2
  ERROR // TR2
  SYNC // TR2

class Connection:
  embeds EventEmitter<ConnectionState, ConnectionStateChange> // RTN4a, RTN4e
  errorReason: ErrorInfo? // RTN14a
  id: String? // RTN8
  key: String? // RTN9
  recoveryKey: String? // RTN16b, RTN16c
  serial: Int // RTN10
  state: ConnectionState // RTN4d
  close() // RTN12
  connect() // RTC1b, RTN3, RTN11
  ping() => io // RTN13

enum ConnectionState:

class ConnectionStateChange:
  current: ConnectionState // TA2
  previous: ConnectionState // TA2
  reason: ErrorInfo? // RTN4f, TA3
  retryIn: Duration? // RTN14d, TA2

class Stats:
  all: StatsMessageTypes //
  apiRequests: StatsRequestCount //
  channels: StatsResourceCount //
  connections: StatsConnectionTypes //
  inbound: StatsMessageTraffic //
  intervalGranularity: StatsIntervalGranularity
  intervalId: String
  intervalTime: Time
  outbound: StatsMessageTraffic //
  persisted: StatsMessageTypes //
  tokenRequests: StatsRequestCount //

enum StatsIntervalGranularity:

class ErrorInfo:
  code: Int // TI1
  message: String // TI1
  statusCode: Int // TI1

class EventEmitter<Event, Data>:
  on((Data...) ->) // RTE4
  on(Event, (Data...) ->) // RTE4
  once((Data...) ->) // RTE4
  once(Event, (Data...) ->) // RTE4
  off() // RTE5
  off((Data...) ->) // RTE5
  off(Event, (Data...) ->) // RTE5
  emit(Event, Data...)  // RTE6

class PaginatedResult<T>:
  items: [T] // TG3
  first() => io PaginatedResult<T> // TG5
  hasNext() -> Bool // TG6
  isLast() -> Bool // TG7
  next() => io PaginatedResult<T>? // TG4

Back to top