Skip to main content

Connecting to a server

Claude
Authored by Claude · Last updated AI generated documentation written by reading the source code
self-hosted

An app talks to the Media Server over HTTP. Every API path starts with /api/v1, and the same API is available at every address the server answers on.

Where the app isAddressExample
On the server's networkThe server's LAN address and porthttp://192.168.1.40:24900
Anywhere, through the owner's reverse proxyThe owner's custom domainhttps://media.example.com
Anywhere, through Remote AccessAn address found with the negotiation APIhttps://abc123.connect.cardinalapps.host:24900

The Quick Start publishes port 24900. A server can be published on any port, so let the user enter the address rather than assuming one.

Checking an address​

GET /api/v1/instance requires no authentication, and is the cheapest way to confirm that an address reaches a Media Server:

curl http://192.168.1.40:24900/api/v1/instance
{
"instanceId": "abc123",
"serverName": "Living Room",
"kioskMode": false
}

The instanceId identifies the server, and it is what Remote Access uses to find it. Store it alongside the address when the app first connects. It only changes in the cases listed under Direct Connection, such as a factory reset. The owner can also read it in the Admin app, on the Overview page's Media Server card.

Remote Access direct connections​

When the server's owner has turned on Remote Access with direct connections enabled, an app can reach the server from anywhere over HTTPS, with a certificate that Cardinal issues to the server. The connection goes straight to the server; it does not pass through Cardinal's infrastructure.

The address to use depends on where the app is, so the app asks for it:

 ┌──────────┐ 1. GET /connect/{instanceId}  ┌───────────────────────┐
│ │──────────────────────────────▶│ api.cardinalapps.host │
│ │◀──────────────────────────────│ (negotiation API) │
│ Your app │ candidates, LAN first └───────────────────────┘
│ │
│ │ 2. GET /api/v1/instance on each candidate, in order
│ │──────────────────────────────▶┌───────────────────────┐
│ │ 3. first to answer = the base │ Cardinal Media Server │
└──────────┘ URL for every API request └───────────────────────┘

Fig. 1 — finding a direct address

1. Ask the negotiation API​

curl https://api.cardinalapps.host/connect/abc123

The endpoint needs no authentication. It responds with:

{
"online": true,
"candidates": [
{ "kind": "lan", "hostname": "192-168-1-40.abc123.connect.cardinalapps.host", "port": 24900 },
{ "kind": "wan", "hostname": "203-0-113-7.abc123.connect.cardinalapps.host", "port": 24900 }
],
"relay": { "url": "https://relay.cardinalapps.host/relay/abc123", "enabled": true }
}
FieldMeaning
onlineWhether the server is connected to Remote Access right now. When false, there are no candidates.
candidatesDirect addresses, in order of preference: lan (the server's own network) first, then wan (the internet). Empty when the owner has turned direct connections off.
candidates[].schemeAbsent in production, which means https.
relayThe relay address. Third-party apps cannot use it yet: see The relay.

Each candidate's hostname encodes an IP address and is covered by the server's certificate. Build the URL as https://{hostname}:{port}, leaving the port out when it is 443.

StatusMeaning
200The server is known. Check online.
404No server with that instance ID uses Remote Access.
400The instance ID is malformed.
429Too many requests from this IP address. The limit is per minute; wait and retry.

Responses can be cached for 10 seconds.

2. Probe the candidates​

Try each candidate in the order given, with GET /api/v1/instance and a short timeout (about 2 seconds), and use the first one that answers with a 2xx. Try them one at a time: the order encodes preference, so racing them can pick a slower route that happened to answer first.

A lan candidate only answers when the app is on the server's network, which is why it comes first. Away from home it does not answer, and the walk moves on to the wan candidate when its timeout expires.

3. Use the address​

Use the winning candidate as the base URL for every request, with the same credential as on the LAN. When requests to it start failing, for example because the device moved between networks, negotiate again. Connecting and staying connected has the full loop: when to walk again, and how to move requests and streams to a new address.

The reference implementation of this walk is the @cardinalapps/remote-access library in the self-hosted source code.

Calling the negotiation API from a browser​

The negotiation API only returns CORS headers to Cardinal's own web apps and to pages served from the server's own Remote Access hostnames. A web app on its own domain cannot call it from the browser. Native apps and server-side code are not affected by CORS and can call it directly.

A web app on its own domain can instead ask the user for the server's Public URL, which the owner finds in the Admin app under Cloud Services → Remote Access → Configure. See Web apps and CORS for what else a browser app needs.

When no candidate answers​

The relay​

Cardinal's own apps fall back to the relay when no direct candidate answers. The relay only admits requests that carry a Cardinal Account token, which third-party apps cannot obtain yet, so it is not available to them. Relay access for third-party apps is planned.

Was this article helpful?