Connecting to a server
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 is | Address | Example |
|---|---|---|
| On the server's network | The server's LAN address and port | http://192.168.1.40:24900 |
| Anywhere, through the owner's reverse proxy | The owner's custom domain | https://media.example.com |
| Anywhere, through Remote Access | An address found with the negotiation API | https://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 }
}
| Field | Meaning |
|---|---|
online | Whether the server is connected to Remote Access right now. When false, there are no candidates. |
candidates | Direct 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[].scheme | Absent in production, which means https. |
relay | The 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.
| Status | Meaning |
|---|---|
200 | The server is known. Check online. |
404 | No server with that instance ID uses Remote Access. |
400 | The instance ID is malformed. |
429 | Too 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
- LAN candidates fail on some home networks. Their hostnames resolve to private addresses, which DNS rebinding protection on some routers blocks. See Troubleshoot issues with DNS rebinding protection.
- Browsers may ask before reaching the local network. Chrome holds a page's first request to a private address until the user allows it. See Your browser asks to allow access to the local network.
- WAN candidates need a working port forward. See Getting the connection through your router.
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?