Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 51 Next »


The Exchange Streaming API provides low latency access to Betfair Exchange market data allowing you to subscribe to and efficiently track changes to market, price and order data.  

The protocol is based on ssl sockets (normal) with a CRLF json protocol.  We publish a definition of the schema of the json messages in the Swagger format.

Swagger Definition

We provide a Swagger schema to allow specification browsing & code generation for various languages; please use:


Market Stream: Swagger DefinitionESASwaggerSchema.json 

Json Deserializers

Expect fields to be added to json responses as and when the need arises; your deserialization code should be tolerant of additional tags.


To generate a client for the programming language of your choice:

  1. Navigate to
  2. Download schema file from ESASwaggerSchema.json and save this to your computer. 
  3. Selection File > Import File
  4. Choose the "ESASwaggerSchema.json" file > Generate Client > Select your required language to generate a language binding.

A few points to note with Swagger:

  • It's cross platform and we can't control how it works / behaves - but it does save a lot of error prone typing.
  • Enums and Inheritance are a little flaky:
    • Enums for error codes / filters etc. are defined but are treated as strings in c# (so you will need to copy definitions from the swagger spec until this is fixed by swagger).
    • Inheritance is defined but not generated correctly - you will have to manually manipulate the op=<type> field
      • In c# JsonCreationConverter is the typical way to model inheritance
      • In java look at JsonSubTypes
  • We are not a REST service - so only the swagger generated model package is relevant

Sample Applications

Betfair support sample applications are available for both C# and Java via

Typical Interactions with Stream API:

The typical API interactions are documented below (detail is below this).


Market Stream:

Order Stream:




Every message is in json & terminated with a line feed (CRLF):

{json message}\r\n


Json Serializer Setup

As the protocol is CRLF delimited don't forget to turn-off Json pretty printing (C# has this on by default)

TCP / SSL Connection

Connection is established with an SSL socket to the following address:

External (SSL):

Avoiding TIMEOUT on connection

Once you have established a connection you should send a message within 15 seconds to avoid receiving a TIMEOUT error


Pre-production (beta) endpoint

For pre-production (beta) releases the following URL should be used for integration testing only.


Integration Endpoint

Basic Message Protocol

Two base message classes exist:

  • RequestMessage - These are messages sent to the server.
  • ResponseMessage - These are messages received from the server.

Every child message type has:

  • id - A unique counter you should supply on a RequestMessage and which will be supplied back on a ResponseMessage.
  • op - This identifies the request type and may be used to switch / deserialize correctly

Note: Any fields representing time and having a long type will represent the UNIX Timestamps (See for conversions) 


RequestMessage is the base class for requests from the client; the discriminator is op=<message type>

Key fields:

  • op=authentication - The AuthenticationMessage - authenticates your connection.
  • op=marketSubscription - The MarketSubscriptionMessage - subscribes to market changes.
  • op=orderSubscription - The OrderSubscriptionMessage - subscribes to order changes.
  • op=heartbeat - The HeartbeatMessage - use if you need to keep a firewall open or want to test connectivity.


  • Remember to set op=<message type> - otherwise we can't decode the request
  • Remember to set id=<unique sequence> - this will let you link requests with responses (these should be logged and provided on support calls)
  • Every RequestMessage will receive a StatusMessage with the status of the call (linked by the id that you send).
    • All errors apart from SUBSCRIPTION_LIMIT_EXCEEDED close the connection


ResponseMessage is the base class for responses back to the client; the discriminator is op=<message type>

Key fields:

  • op=connection - The ConnectionMessage sent on your connection.
  • op=status - The StatusMessage (returned in response to every RequestMessage)
  • op=mcm - The MarketChangeMessage that carries the initial image and updates to markets that you have subscribed to.
  • op=ocm - The OrderChangeMessage that carries the initial image and updates to orders that you have subscribed to.


As mentioned earlier the id=<request id> and links your request with your response.

ChangeMessages carry the id of the original request that established the subscription

Status / StatusMessage

Every request receives a status response with a matching id.

Key fields:

  • statusCode -  The status of the request i.e success / fail

    • SUCCESS - Call processed correctly
    • FAILURE - Call failed (inspect errorCode and errorMessage for reason)
  • connectionClosed - Boolean set to true if the connection was closed as a result of a failure
  • errorCode - The type of error in case of a failure - see the swagger spec / enum.

  • errorMessage - Additional message in case of a failure


This categorizes the various error codes that could be expected (these are subject to change and extension)

Protocol   General errors not sent with id linking to specific request (as no request context)
Failure code returned when an invalid input is provided (could not deserialize the message)
Failure code when a client times out (i.e. too slow sending data)
Authentication Specific to authentication
Failure code returned when an application key is not found in the message
Failure code returned when an invalid application key is received
Failure code returned when a session token is not found in the message
Failure code returned when an invalid session token is received
Failure code returned when client is not authorized to perform the operation
Failure code returned when a client tries to create more connections than allowed to
Subscription   Specific to subscription requests
Customer tried to subscribe to more markets than allowed to
Failure code returned when an invalid clock is provided on re-subscription (check initialClk / clk supplied)
 General  General errors which may or may not be linked to specific request id
Failure code returned when an internal error occurred on the server
Failure code used when the client / server connection is terminated

Connection / ConnectionMessage

This is received by the client when it successfully opens a connection to the server

Key fields:

  • connectionId - This is a unique identifier that you must supply for support.

Initial ConnectionMessage

On establishing a connection a client receives a ConnectionMessage - the connectionId must be logged & supplied on any support queries:


Authentication / AuthenticationMessage

This message is the first message that the client must send on connecting to the server - you must be authenticated before any other request is processed.  

Key fields:

  • op=authentication - This is the operation type
  • appKey - This is your application key to identify your application
  • session - The session token generated from API login.

Common Authentication Errors

Some common authentication errors that you should handle - these are defined on ErrorCodes enum (these will all close your connection):

  • NO_APP_KEY / INVALID_APP_KEY - Check you are using the correct app key
  • NO_SESSION / INVALID_SESSION_INFORMATION - Check the session is current
  • NOT_AUTHORIZED - Check that you are using the correct appkey / session and that it has been setup by BDP

  • MAX_CONNECTION_LIMIT_EXCEEDED - Check that you are not creating too many connections / are closing connections properly.

Subscription / SubscriptionMessage

This message changes the client's subscription - there are currently two subscription message types:

  • op=marketSubscription- MarketSubscriptionMessage which streams:
    • op=mcm - MarketChangeMessage - the price changes for a market
  • op=orderSubscription- OrderSubscriptionMessage which streams:
    • op=ocm - OrderChangeMessage - the order changes for a market

On creating a subscription you will receive:

  • StatusMessage confirming the status of your request
  • A stream of ChangeMessages linked with the id of the request which is composed of:
    • Initial image
    • Deltas to the initial image

It is possible to subscribe multiple times - each replaces the previous (each will send a new initial image and deltas) - they are not additive.

Key fields on a SubscriptionMessage:

  • segmentationEnabled=true - Segmentation will shortly be switched on by default (so please code with this set)
    • segmentation breaks up large messages and improves: end to end performance, latency, time to first and last byte
    • see the topic on change message segmentation for a full explanation of how this works.
  • conflateMs - Specifies a forced conflation rate (in milliseconds)
  • heartbeatMs - Specifies a minimum interval that a client would expect to receive a message (in milliseconds)
    • If no change is delivered in this interval then an empty change message will be sent with a ChangeType.HEARTBEAT
  • initialClk & clk - these two sequence tokens allow for faster recovery in the event of a disconnection:
    • If supplied (with identical subscription criteria) you will receive a delta to your previous state rather than a full initial image
    • see the topic on re-subscription for a full explanation of how this works.


This message is the payload that delivers changes (both initial image & updates) to a client - there are currently two change message types:

  • op=mcm - MarketChangeMessage
  • op=ocm - OrderChangeMessage

Key fields on a ChangeMessage:

  • ct= ChangeType - this enumeration is used to identify the type of change
    • SUB_IMAGE - The initial image returned from a subscribe
    • RESUB_DELTA - A patch returned from a resubscribe
    • HEARTBEAT - An empty message published if no data has been sent within heartbeatMs
      • We send these to maintain the connection to you and detect closed connections
      • You can use the heartbeatMs to verify that you are still connected
    • <null / not set> - An update message
  • segmentType - SegmentType - this enumeration identifies multi-part segmented messages:
    • SEG_START - Start of a segmented message
    • SEG - Middle part of a segmented message
    • SEG_END - Last part of a segmented message
    • <null / not set> - A non-segmented message
  • conflateMs - the actual conflation being used 
    • This might be different to what you specified - if you account is for instance delayed or your request was out of bounds
  • heartbeatMs - the actual heartbeat being used
    • This might be different to what you specified as we bounds check
    • You can use this to verify your connection is live (as you should receive 1 message within this time period).
  • pt - publishTime - the time we sent the message
  • initialClk & clk - these two sequence tokens allow for faster recovery in the event of a disconnection:
    • If we send these then they should be stored
    • see the topic on re-subscription for a full explanation of how this works.

Heartbeat ChangeMessages

heartbeatMs is a guarantee of how often (even with no changes) you will receive a ChangeMessage; i.e.:

If heartbeatMs= 500 and your subscription has not changed in 500ms then we will send an empty ChangeMessage with ct=HEARTBEAT

(this verifies your connection is live and processing data)

Change Message Segmentation

The below shows the key interactions for subscription & changes with segmentation applied:

Typically on changing your subscription you will want to clear any local cache you maintain.

Initial Image Handling

  • How can I detect the start of an initial image & clear my cache?
    • ct=ChangeType.SUB_IMAGE and segmentType=null or SegmentType.SEG_START indicates the start of a new image
  • How can I detect the end of an initial image?
    • ct=ChangeType.SUB_IMAGE and segmentType=null or SegmentType.SEG_END indicates the end of a new image
  • When I change Subscription how do I safely ignore messages for a previous subscription?
    • All ChangeMessages carry have id=<request id> this allows safe disposal during subscription change


This subscription type is used to receive price changes for one or more markets; your subscription criteria determine what you see.


Coarse vs Fine Grain Subscriptions

It is preferable to use coarse grain subscriptions (subscribe to a super-set) rather than fine grain (specific market ids).

  • If you find yourself frequently changing subscriptions you probably want to find a wider super-set to subscribe to


A MarketSubscription has two types of filter:

  • marketFilter - MarketFilter - this is a horizontal filter of markets that you require (i.e. rows)
  • marketDataFilter - MarketDataFilter - this is a vertical filter of fields that you require (i.e. columns)

Limiting the amount of data that you consume will make your initial image much smaller (and faster) & suppress changes that are uninteresting to you.

Market Filtering / MarketFilter

As with the APING API users have the ability to filter the market data they get from the new Exchange Stream API (ESA).

All subscriptions are evaluated with a few default criteria:

  • Standard jurisdictional filtering that restricts visibility (mirroring site behavior)
  • Permissions that control:
    • Specific sports that you are entitled for
    • A maximum consumption limit (exceeding this will result in an error with details of the limit: ErrorCode.SUBSCRIPTION_LIMIT_EXCEEDED)

 Users can then specify the following filters when they subscribe to ESA:

Filter nameTypeMandatoryDescription




If no marketIds passed user will be subscribed to all markets




Restrict to bsp markets only, if True or non-bsp markets if False. If not specified then returns both BSP and non-BSP markets




Restrict to markets that match the betting type of the market (i.e. Odds, Asian Handicap Singles, or Asian Handicap Doubles)




Restrict markets by event type associated with the market. (i.e., "1" for Football, "7" for Horse Racing, etc)




Restrict markets by the event id associated with the market.




Restrict to markets that will turn in play if True or will not turn in play if false. If not specified, returns both.




Restrict to markets that match the type of the market (i.e., MATCH_ODDS, HALF_TIME_SCORE). You should use this instead of relying on the market name as the market type codes are the same in all locales




Restrict markets by the venue associated with the market. Currently only Horse Racing markets have venues.




Restrict to markets that are in the specified country or countries

For example a subscription message with almost all filters enabled will look something like this:  



Subscriptions with no matching markets

We don't verify your subscription criteria as you could potentially subscribe to either a wild card (which would include future markets) or a future marketid which we do not have yet but would send on arrival

Market data field filtering / MarketDataFilter

A market data filter restricts the fields that you get back (and only if the fields have changed).

Key fields:

  • fields - A set of field filter flags (see below)
  • ladderLevels - For depth based ladders the number of levels to send (1 to 6)

The field filter flags are defined as:
Filter nameFields:TypeDescription


bdatb, bdatl

level, price, size

Best prices including virtual prices - depth is controlled by ladderLevels (1 to 6)


batb, batl

level, price, sizeBest prices not including virtual prices - depth is controlled by ladderLevels (1 to 6)


atb, atl

price, sizeFull available to BACK/LAY ladder



price, sizeFull traded ladder



sizeMarket and runner level traded volume



priceLast traded price



MarketDefinitionSend market definitions.


spb, spl

price, sizeStarting price ladder


spn, spf

priceStarting price projection prices

Multiple field filters may be combined; a subscription message that contains data fields should look like the following: 


Correctly configuring field filters

Correctly configuring field filters can help by:

  • Reducing the size (and time) of initial images
  • Reducing the rate of change (as only changes matching your field filter are sent)


MC / MarketChangeMessage

This is the ChangeMessage stream of data we send back to you once you subscribe to the market stream. 

Key fields:

  • <as for ChangeMessage>
  • mc / MarketChange - this list of market changes contains the changes the markets that you have subscribed to.  
    • img / Image - replace existing prices / data with the data supplied: it is not a delta (or null if delta)
    • marketDefinition / MarketDefinition - this is sent in full (but only if it has changed)
    • rc / RunnerChange - this is sent to supply the details of a runner (namely prices)
      • con / Conflated = true - if this is sent then more than one change is combined in this message (purely informational).
      • Values - Please note: these are only sent if the value has changed.
        • tv - Traded Volume
        • ltp - Last Traded Price
        • spn - Starting Price Near
        • spf - Starting Price Far
      • Level / Depth Based Ladders (level, price, size - triples - keyed by level):
        • size=0 - indicates a remove
        • batb / batl - Best Available To Back / Best Available To Lay (non-virtual)
        • bdatb / bdatl - Best Display Available To Back / Best Display Available To Lay (virtual)
      • Price point / full depth Ladders (price, size - tuples - keyed by price):
        • size=0 - indicates a remove
        • atb / atl - Available To Back / Available To Lay (these are the raw / full depth non-virtual prices)
        • spb / spl - Starting Price Back / Starting Price Lay
        • trd - Traded 
Building a price cache

Most of the change based data (RunnerChange) is delta based - this means a few rules:

  • img / Image - if this is set to true then you should replace this item in your cache
  • Values - the values sent are nullable & are not sent if they are not changed (i.e. if tv has not changed then there will be no field in the message)
  • Level / Depth Based ladders
    • [0, 1.2, 20] -> Insert / Update level 0 (top of book) with price 1.2 and size 20
    • [0, 1.2, 0] -> Remove level 0 (top of book) i.e. ladder is now empty
  • Price point / full depth ladders
    • [1.2, 20] -> Insert / Update price 1.2 with size 20
    • [1.2, 0] -> Remove price 1.2 i.e. there is no size at this price


This subscription type is used to receive order changes; the subscription message has one type of filter

  • orderFilter (optional)


This optional filter already filters by your account; but additional data shaping is supported

Filter nameTypeMandatoryDefaultDescription





This is for internal use only & should not be set on your filter (your subscription is already locked to your account).





Returns overall / net position (OrderRunnerChange.mb /





Restricts to specified customerStrategyRefs; this will filter orders and StrategyMatchChanges accordingly (Note: overall postition is not filtered)





Returns strategy positions (OrderRunnerChange.smc=Map<customerStrategyRef, StrategyMatchChange>) - these are sent in delta format as per overall position.

OCM / OrderChangeMessage

This is the ChangeMessage stream of data we send back to you once you subscribe to the order stream. 

Key fields:

  • <as for ChangeMessage>
  • oc / OrderAccountChange - the modifications to account's orders (will be null on a heartbeat)
    • closed - indicates when the market is closed
    • id / Market Id - the id of the market the order is on
    • orc / Order Changes - a list of changes to orders on a runner
      • id / Selection Id - the id of the runner (selection) 
      • uo / Unmatched Orders - orders on this runner that are unmatched
        • Every order change is sent in full; the transient on a change to EXECUTION_COMPLETE is sent (but it would not be sent on initial image)
        • id / Bet Id - the id of the order  
        • p / Price - the original placed price of the order
        • s / Size - the original placed size of the order
        • bsp / BSP Liability - the BSP liability of the order (null if the order is not a BSP order)
        • side / Side - the side of the order
        • status / Status - the status of the order (E = EXECUTABLE, EC = EXECUTION_COMPLETE)
        • pt / Persistence Type - whether the order will persist at in play or not (L = LAPSE, P = PERSIST, MOC = Market On Close)
        • ot / Order Type - the type of the order (L = LIMIT, MOC = MARKET_ON_CLOSE, LOC = LIMIT_ON_CLOSE)
        • pd / Placed Date - the date the order was placed
        • md / Matched Date - the date the order was matched (null if the order is not matched)
        • avp / Average Price Matched - the average price the order was matched at (null if the order is not matched
        • sm / Size Matched - the amount of the order that has been matched
        • sr / Size Remaining - the amount of the order that is remaining unmatched
        • sl / Size Lapsed - the amount of the order that has been lapsed
        • sc / Size Cancelled - the amount of the order that has been cancelled
        • sv / Size Voided - the amount of the order that has been voided
        • rac / Regulator Auth Code - the auth code returned by the regulator
        • rc / Regulator Code - the regulator of the order
        • rfo / Reference Order - the customer supplied order reference
        • rfs / Reference Strategy - the customer supplied strategy reference used to group orders together - default is ""
      • Price point / full depth Ladders (price, size - tuples - keyed by price) of matches:
        • mb / Matched Backs - matched amounts by distinct matched price on the Back side for this runner
        • ml / Matched Lays - matched amounts by distinct matched price on the Lay side for this runner
Building an order cache

An order cache is somewhat simpler as orders are sent in full (on change) and only matches need delta merging

  • img / Image - if this is set to true then you should replace this item in your cache
  • Orders - replace each order according to order id.
  • Price point / full depth ladders
    • [1.2, 20] -> Insert / Update price 1.2 with size 20
    • [1.2, 0] -> Remove price 1.2 i.e. there is no size at this price
    • An empty list of points also means the ladder is now empty



Market subscriptions - are always in underlying exchange currency - GBP. The default roll-up for GBP is £2 for batb / batl and bdatb / bdatl, This means that stakes of less than £2 (or currency equivalent) are rolled up to the next available price on the odds ladder. For atb / atl there is no roll-up. Available volume is displayed at all prices including those with less than £2 available.

Orders subscriptions - are provided in the currency of the account that the orders are placed in.

Unmatched Orders

New subscriptions: Will receive an initial image with only E - Executable orders (unmatched).

Live subscriptions: Will receive a transient of the order to EC - Execution Complete as the order transits into that state (allowing you to remove the order from your cache).

Example Output of Order Stream Message on Connection/Re-connection

Here's an example showing the data provided following a connection/re-connection to the Order Stream API.  The example shows matched backs on two separate markets one of which has a size remaining of 0.25.


Example of Order Stream Output (reconnection) - with size remaining
	"op": "ocm",
	"id": 6,
	"initialClk": "GpOH0JwBH762w50BHKKomJ0BGpzR5ZoBH5mWsJwB",
	"conflateMs": 0,
	"heartbeatMs": 5000,
	"pt": 1468943673782,
	"ct": "SUB_IMAGE",
	"oc": [{
		"id": "1.125657695",
		"orc": [{
			"fullImage": true,
			"id": 48756,
			"mb": [
				[1.4, 2]
	}, {
		"id": "1.125657760",
		"orc": [{
			"fullImage": true,
			"id": 151478,
			"uo": [{
				"id": "71352090695",
				"p": 12,
				"s": 5,
				"side": "B",
				"status": "E",
				"pt": "L",
				"ot": "L",
				"pd": 1468919099000,
				"md": 1468933833000,
				"avp": 12,
				"sm": 4.75,
				"sr": 0.25,
				"sl": 0,
				"sc": 0,
				"sv": 0
			"mb": [
				[12, 4.75]


Remaining 0.25 is then matched on marketId 1.125657760

Example of Order Stream Output - with size remaining matched
 	"op": "ocm",
 	"id": 10,
 	"initialClk": "GtD10ZwBH5OJxZ0BHK75mZ0BGsKq6JoBH4THsZwB",
 	"clk": "AAAAAAAAAAAAAA==",
 	"conflateMs": 0,
 	"heartbeatMs": 5000,
 	"pt": 1468944647413,
 	"ct": "SUB_IMAGE",
 	"oc": [{
 		"id": "1.125670254",
 		"orc": [{
 			"fullImage": true,
 			"id": 5643663
 	}, {
 		"id": "1.125657760",
 		"orc": [{
 			"fullImage": true,
 			"id": 151478,
 			"mb": [
 				[12, 5]
 	}, {
 		"id": "1.125657695",
 		"orc": [{
 			"fullImage": true,
 			"id": 48756,
 			"mb": [
 				[1.4, 2]


Heartbeat / HearbeatMessage

This is an explicit heartbeat request (in addition to server heartbeat interval which is automatic).

This functionality should not normally be necessary unless you need to keep a firewall open.

Do I need to use HeartbeatMessage?

No - under normal circumstances the subscription level ChangeType.HEARTBEAT is an acceptable guarantee of connection health.

Use the HeartbeatMessage only if you need to keep a firewall open - as it will incur some performance penalty (as a response will block your connection)

Re-connection / Re-subscription

If a client is disconnected a client may connect, authenticate and re-subscribe.

Prerequisite steps:

  • Store your subscription criteria (re-subscribe will only work correctly with identical subscription critieria
  • Store initialClk (normally only initial image) & Clk (normally on every non-segmented message or a SEG_END) on any change message they are sent on.

Connection is broken.

  • Connect & Authenticate as normal
  • Subscribe setting initialClk and Clk to the last values sent on the subscription
  • Change message with ChangeType.RESUB_DELTA is sent - this will patch your cache
  • Some markets might have img=true set indicating they are either new or can't be patched.

Easiest way to implement re-subscribe

  • Store any new subscription message you send as a "pending subscription"
  • Store this as a "active subscription" once you get your initial image
  • Update the initialClk & clk on the subscription message with any non-null values
  • Resend this message after re-connecting

Performance Considerations

Here are a few tips on performance which are worth bearing in mind:


Performance tips

  • A single market subscription & a subscription to all markets have an identical latency:
    • Cost is identical as the two subscriptions above would evaluate in sequence and thus with the same average latency.
    • Initial image is more costly to send than extra updates.
    • Limiting data with appropriate filters reduces initial image time
  • Segmented data will always out perform non-segmented data:
    • You will be processing a buffer while another is in-flight and another is being prepared to send
  • Writes to your connection are directly effected by how quickly you consume data & clear your socket's buffer
    • Consuming data slowly is effectively identical to setting conflation.
    • If you receive conf=true flag on a market - then you are consuming data slower than the rate of deliver

Currency Support

The Exchange Stream API supports GBP currency only.

Those looking to convert data from GBP to a different currency should use listCurrencyRates to do so.



Market subscriptions - are always in underlying exchange currency - GBP. The default roll-up for GBP is £2 for batb / batl and bdatb / bdatl, This means that stakes of less than £2 (or currency equivalent) are rolled up to the next available price on the odds ladder. For atb / atl there is no roll-up. Available volume is displayed at all prices including those with less than £2 available.

Orders subscriptions - are provided in the currency of the account that the orders are placed in.

Runner Removals on the Order Stream

When a Rule 4 Runner Removal occurs in a Horse Race the price of matched bets on remaining runners are reduced by a Reduction Factor.

For these matched bets, you will receive on the Order Stream both a uo for the affected bet and the relevant updates to mb or ml (reducing the matched volume at the original matched price and adding volume at the new reduced price).


Initial bet placement at price 12




Bet fully matched at price 12




Runner removed (and so bet reduced in price to 9.47)




See the avp in the uo record showing the new price of 9.47 and see the two entries in mb, one to remove the previously added size of 2 at price point 12 and one to add the size of 2 into the new price point of size 9.47.


Bets placed on the actual removed runner will be voided/lapsed (for matched/unmatched bets respectively), these updates will not be sent through the order stream. The advice to consumers should be to also consume the market stream, detect that the runner has been marked as REMOVED in an update to the Market Definition and to therefore consider all bets held on that runner to be Void (or Lapsed for unmatched bets).

Sample Applications - C# & Java

A console based C# & Java sample application is available for the Market & Order Streaming API and is available via