Jump to content

SimTX API/Device login: Difference between revisions

From SimTX Wiki
Clanker (talk | contribs)
Clanker moved page SimTX API/Device login to SimTX API v24/Device login: Archive the v24 protocol reference
Tag: New redirect
 
Clanker (talk | contribs)
Remove development history and server internals
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
#REDIRECT [[SimTX API v24/Device login]]
{{SimTX API navigation}}
'''Device login''' is the sign-in flow for software that cannot, or should not, handle a SimTX password itself: desktop applications, command-line tools and headless bridges. The software shows the user a short code, the user approves it in a browser while signed in to SimTX, and the software receives an [[SimTX API/Account service#Credential formats|access and refresh credential]].
 
The flow is modelled on the OAuth 2.0 device authorization grant (RFC 8628), with two differences: requests and responses are JSON rather than form-encoded, and the endpoint and field names are SimTX's own. Generic OAuth libraries will therefore not work without adaptation.
 
Software that only receives at an ad-hoc site does not need to sign in at all. Bots that run unattended should normally use a personal credential instead (see [[SimTX API/Account service#Authentication|Authentication]]).
 
== Sequence ==
<syntaxhighlight lang="text">
Client                          auth.simtx.net                    User's browser
  |  POST /device/authorizations      |                                  |
  |---------------------------------->|                                  |
  |  201 deviceCode, userCode, URI    |                                  |
  |<----------------------------------|                                  |
  |  show userCode, open URI ------------------------------------------->|
  |                                  |  user signs in and approves    |
  |  POST /device/token  (every 5 s)  |<---------------------------------|
  |---------------------------------->|                                  |
  |  400 authorization_pending        |                                  |
  |<----------------------------------|                                  |
  |  POST /device/token              |                                  |
  |---------------------------------->|                                  |
  |  200 access + refresh credential  |                                  |
  |<----------------------------------|                                  |
</syntaxhighlight>
 
== Starting a login ==
<code>POST /api/v1/device/authorizations</code>. No authentication.
 
=== Request ===
<syntaxhighlight lang="json">
{ "clientName": "Example SDR Client", "scope": "game.transmit station.manage" }
</syntaxhighlight>
 
{| class="wikitable"
! Field !! Type !! Required !! Description
|-
| <code>clientName</code> || string || No || Name shown to the user on the approval page and in their device list. Truncated to 80 characters. Defaults to <code>SimTX client</code> when blank.
|-
| <code>scope</code> || string || No || Space-separated list of [[SimTX API/Permissions|permission identifiers]] the credential may use. An empty or missing scope requests every permission the account holds at the moment of approval. Unknown identifiers are rejected with <code>400 scope_invalid</code>.
|}
 
Request the smallest scope that does the job. A transmitter needs <code>game.transmit</code>. A client that creates or edits stations, launches satellites or deorbits them also needs <code>station.manage</code>. A tool that publishes extensions needs <code>catalog.publish</code>. Reading the user's stations and receiving through them need no particular scope.
 
=== Response ===
<code>201 Created</code>:
 
<syntaxhighlight lang="json">
{
  "deviceCode": "stx_dc1_9a1f...",
  "userCode": "QZ7K-M4TP",
  "verificationUri": "https://auth.simtx.net/device",
  "verificationUriComplete": "https://auth.simtx.net/device?code=QZ7K-M4TP",
  "expiresIn": 600,
  "interval": 5,
  "expiresAt": "2026-10-24T12:10:00Z"
}
</syntaxhighlight>
 
{| class="wikitable"
! Field !! Type !! Description
|-
| <code>deviceCode</code> || string || Secret used to poll for the result. Keep it private; do not show it to the user.
|-
| <code>userCode</code> || string || Code the user confirms in the browser.
|-
| <code>verificationUri</code> || string || Page where the user types the code.
|-
| <code>verificationUriComplete</code> || string || Same page with the code pre-filled. Open this one if the client can launch a browser.
|-
| <code>expiresIn</code> || integer || Seconds until the device code expires (600).
|-
| <code>interval</code> || integer || Minimum seconds between polls (5).
|-
| <code>expiresAt</code> || string || Absolute expiry time.
|}
 
Display <code>userCode</code> prominently and open <code>verificationUriComplete</code>.
 
== Polling for credentials ==
<code>POST /api/v1/device/token</code>. No authentication.
 
<syntaxhighlight lang="json">
{ "deviceCode": "stx_dc1_9a1f..." }
</syntaxhighlight>
 
=== Success ===
<code>200 OK</code> with a credential pair:
 
<syntaxhighlight lang="json">
{
  "accessCredential": "stx_at1_5c0e...",
  "refreshCredential": "stx_rt1_b77d...",
  "credentialType": "Bearer",
  "expiresIn": 600,
  "scope": "game.transmit station.manage"
}
</syntaxhighlight>
 
{| class="wikitable"
! Field !! Type !! Description
|-
| <code>accessCredential</code> || string || Bearer credential, valid for <code>expiresIn</code> seconds.
|-
| <code>refreshCredential</code> || string || Used once to obtain the next pair.
|-
| <code>credentialType</code> || string || Always <code>Bearer</code>.
|-
| <code>expiresIn</code> || integer || Access credential lifetime in seconds (600).
|-
| <code>scope</code> || string || Space-separated scopes actually granted.
|}
 
A device code can be exchanged exactly once. Polling again after success returns <code>access_denied</code>.
 
=== Not ready yet ===
Until the user acts, the service answers <code>400</code> with one of the following codes. Every one of these responses includes the interval the client must now use, as a string, in <code>fieldErrors.interval</code>.
 
{| class="wikitable"
! <code>code</code> !! Meaning !! Client action
|-
| <code>authorization_pending</code> || The user has not approved or denied yet. || Wait <code>interval</code> seconds and poll again.
|-
| <code>slow_down</code> || The poll arrived sooner than <code>interval</code> seconds after the previous one. The interval has been increased by 5 seconds. || Adopt <code>fieldErrors.interval</code> for all later polls.
|-
| <code>expired_token</code> || Ten minutes have passed. || Start a new login.
|-
| <code>access_denied</code> || The user denied the request, or the code was already exchanged. || Stop.
|-
| <code>invalid_grant</code> || The device code is unknown. || Start a new login.
|}
 
A client that ignores <code>slow_down</code> and keeps its old pace is told to slow down on every single poll, and each warning adds another five seconds. Such a client never receives credentials. Read the interval from the response.
 
== Refreshing ==
<code>POST /api/v1/device/refresh</code>. No authentication.
 
<syntaxhighlight lang="json">
{ "refreshCredential": "stx_rt1_b77d..." }
</syntaxhighlight>
 
On success the response is <code>200 OK</code> with a new credential pair in the same format as above. '''The refresh credential rotates on every use.''' Store the new one immediately and discard the old one.
 
{| class="wikitable"
! HTTP !! <code>code</code> !! Meaning
|-
| 401 || <code>invalid_grant</code> || The refresh credential is unknown, expired, or its device session was revoked. Sign in again.
|-
| 401 || <code>refresh_reuse_detected</code> || A refresh credential that had already been used was presented again. The service treats this as theft and revokes the whole device session. Sign in again.
|}
 
Refresh credentials expire after 30 days without use and 180 days after the original sign-in, whichever comes first.
 
== Recommended client behaviour ==
# On start-up, load the stored refresh credential. If there is none, run device login.
# Refresh to obtain an access credential. Keep the access credential in memory.
# Refresh again when fewer than about 30 seconds of the access credential remain.
# When any endpoint answers <code>401 credential_invalid</code>, refresh once and repeat the request. If the refresh fails, delete the stored credential and run device login.
# Serialise refreshes. Two threads refreshing with the same refresh credential at once will trigger <code>refresh_reuse_detected</code>, and the user will have to sign in again.
 
== Example ==
A complete login with <code>curl</code> and <code>jq</code>:
 
<syntaxhighlight lang="bash">
AUTH=https://auth.simtx.net
 
start=$(curl -s -X POST "$AUTH/api/v1/device/authorizations" \
  -H 'Content-Type: application/json' \
  -d '{"clientName":"curl demo","scope":"game.transmit"}')
device=$(jq -r .deviceCode <<<"$start")
interval=$(jq -r .interval <<<"$start")
echo "Open $(jq -r .verificationUriComplete <<<"$start") and confirm $(jq -r .userCode <<<"$start")"
 
while sleep "$interval"; do
  reply=$(curl -s -X POST "$AUTH/api/v1/device/token" \
    -H 'Content-Type: application/json' -d "{\"deviceCode\":\"$device\"}")
  case $(jq -r '.code // "ok"' <<<"$reply") in
    ok) jq . <<<"$reply"; break ;;
    authorization_pending) ;;
    slow_down) interval=$(jq -r .fieldErrors.interval <<<"$reply") ;;
    *) echo "login failed: $reply"; exit 1 ;;
  esac
done
</syntaxhighlight>
 
== See also ==
* [[SimTX API/Account service]]: errors, credential formats and rate limits.
* [[SimTX API/Station endpoints]]: the user's stations.
* [[SimTX API/Game tickets]]: the next step for a client that wants to transmit.
 
{{SimTX API navbox}}
[[Category:SimTX API]]
[[Category:SimTX API account service]]

Latest revision as of 17:16, 24 October 2026

Device login is the sign-in flow for software that cannot, or should not, handle a SimTX password itself: desktop applications, command-line tools and headless bridges. The software shows the user a short code, the user approves it in a browser while signed in to SimTX, and the software receives an access and refresh credential.

The flow is modelled on the OAuth 2.0 device authorization grant (RFC 8628), with two differences: requests and responses are JSON rather than form-encoded, and the endpoint and field names are SimTX's own. Generic OAuth libraries will therefore not work without adaptation.

Software that only receives at an ad-hoc site does not need to sign in at all. Bots that run unattended should normally use a personal credential instead (see Authentication).

Sequence[edit | edit source]

 Client                          auth.simtx.net                    User's browser
   |  POST /device/authorizations      |                                  |
   |---------------------------------->|                                  |
   |  201 deviceCode, userCode, URI    |                                  |
   |<----------------------------------|                                  |
   |  show userCode, open URI ------------------------------------------->|
   |                                   |   user signs in and approves     |
   |  POST /device/token  (every 5 s)  |<---------------------------------|
   |---------------------------------->|                                  |
   |  400 authorization_pending        |                                  |
   |<----------------------------------|                                  |
   |  POST /device/token               |                                  |
   |---------------------------------->|                                  |
   |  200 access + refresh credential  |                                  |
   |<----------------------------------|                                  |

Starting a login[edit | edit source]

POST /api/v1/device/authorizations. No authentication.

Request[edit | edit source]

{ "clientName": "Example SDR Client", "scope": "game.transmit station.manage" }
Field Type Required Description
clientName string No Name shown to the user on the approval page and in their device list. Truncated to 80 characters. Defaults to SimTX client when blank.
scope string No Space-separated list of permission identifiers the credential may use. An empty or missing scope requests every permission the account holds at the moment of approval. Unknown identifiers are rejected with 400 scope_invalid.

Request the smallest scope that does the job. A transmitter needs game.transmit. A client that creates or edits stations, launches satellites or deorbits them also needs station.manage. A tool that publishes extensions needs catalog.publish. Reading the user's stations and receiving through them need no particular scope.

Response[edit | edit source]

201 Created:

{
  "deviceCode": "stx_dc1_9a1f...",
  "userCode": "QZ7K-M4TP",
  "verificationUri": "https://auth.simtx.net/device",
  "verificationUriComplete": "https://auth.simtx.net/device?code=QZ7K-M4TP",
  "expiresIn": 600,
  "interval": 5,
  "expiresAt": "2026-10-24T12:10:00Z"
}
Field Type Description
deviceCode string Secret used to poll for the result. Keep it private; do not show it to the user.
userCode string Code the user confirms in the browser.
verificationUri string Page where the user types the code.
verificationUriComplete string Same page with the code pre-filled. Open this one if the client can launch a browser.
expiresIn integer Seconds until the device code expires (600).
interval integer Minimum seconds between polls (5).
expiresAt string Absolute expiry time.

Display userCode prominently and open verificationUriComplete.

Polling for credentials[edit | edit source]

POST /api/v1/device/token. No authentication.

{ "deviceCode": "stx_dc1_9a1f..." }

Success[edit | edit source]

200 OK with a credential pair:

{
  "accessCredential": "stx_at1_5c0e...",
  "refreshCredential": "stx_rt1_b77d...",
  "credentialType": "Bearer",
  "expiresIn": 600,
  "scope": "game.transmit station.manage"
}
Field Type Description
accessCredential string Bearer credential, valid for expiresIn seconds.
refreshCredential string Used once to obtain the next pair.
credentialType string Always Bearer.
expiresIn integer Access credential lifetime in seconds (600).
scope string Space-separated scopes actually granted.

A device code can be exchanged exactly once. Polling again after success returns access_denied.

Not ready yet[edit | edit source]

Until the user acts, the service answers 400 with one of the following codes. Every one of these responses includes the interval the client must now use, as a string, in fieldErrors.interval.

code Meaning Client action
authorization_pending The user has not approved or denied yet. Wait interval seconds and poll again.
slow_down The poll arrived sooner than interval seconds after the previous one. The interval has been increased by 5 seconds. Adopt fieldErrors.interval for all later polls.
expired_token Ten minutes have passed. Start a new login.
access_denied The user denied the request, or the code was already exchanged. Stop.
invalid_grant The device code is unknown. Start a new login.

A client that ignores slow_down and keeps its old pace is told to slow down on every single poll, and each warning adds another five seconds. Such a client never receives credentials. Read the interval from the response.

Refreshing[edit | edit source]

POST /api/v1/device/refresh. No authentication.

{ "refreshCredential": "stx_rt1_b77d..." }

On success the response is 200 OK with a new credential pair in the same format as above. The refresh credential rotates on every use. Store the new one immediately and discard the old one.

HTTP code Meaning
401 invalid_grant The refresh credential is unknown, expired, or its device session was revoked. Sign in again.
401 refresh_reuse_detected A refresh credential that had already been used was presented again. The service treats this as theft and revokes the whole device session. Sign in again.

Refresh credentials expire after 30 days without use and 180 days after the original sign-in, whichever comes first.

Recommended client behaviour[edit | edit source]

  1. On start-up, load the stored refresh credential. If there is none, run device login.
  2. Refresh to obtain an access credential. Keep the access credential in memory.
  3. Refresh again when fewer than about 30 seconds of the access credential remain.
  4. When any endpoint answers 401 credential_invalid, refresh once and repeat the request. If the refresh fails, delete the stored credential and run device login.
  5. Serialise refreshes. Two threads refreshing with the same refresh credential at once will trigger refresh_reuse_detected, and the user will have to sign in again.

Example[edit | edit source]

A complete login with curl and jq:

AUTH=https://auth.simtx.net

start=$(curl -s -X POST "$AUTH/api/v1/device/authorizations" \
  -H 'Content-Type: application/json' \
  -d '{"clientName":"curl demo","scope":"game.transmit"}')
device=$(jq -r .deviceCode <<<"$start")
interval=$(jq -r .interval <<<"$start")
echo "Open $(jq -r .verificationUriComplete <<<"$start") and confirm $(jq -r .userCode <<<"$start")"

while sleep "$interval"; do
  reply=$(curl -s -X POST "$AUTH/api/v1/device/token" \
    -H 'Content-Type: application/json' -d "{\"deviceCode\":\"$device\"}")
  case $(jq -r '.code // "ok"' <<<"$reply") in
    ok) jq . <<<"$reply"; break ;;
    authorization_pending) ;;
    slow_down) interval=$(jq -r .fieldErrors.interval <<<"$reply") ;;
    *) echo "login failed: $reply"; exit 1 ;;
  esac
done

See also[edit | edit source]

SimTX API reference (protocol v25)
Start OverviewQuick startFramingData typesPacket listSchemaLimits and errorsCompatibility and versioning
Account service OverviewDevice loginServer directoryStation endpointsGame ticketsPermissionsPublic endpoints
Game protocol Connection and handshakeSession statusReceiver sessionTransmitter sessionControl sessionCatalogModeration
Stations StationsSatellitesTracking and pointing
Connection ClientHelloPingServerHelloPongErrorBanner
Session AntennaPointingUpdateSessionStatus
Receiver ForwardedClientAddressReceiverStartReceiverRetuneIqStreamDescriptorIqBlock
Transmitter TransmitterStartSampleBlockTxUpdateTxModeInfoTxQueueTxAcceptTxTelemetryTxUpdateAck
Control ListStationsStartMonitorStopMonitorWeatherMapRequestServerHealthRequestCoverageMapRequestEphemerisRequestPassPredictionRequestStationListMonitorBlockMonitorEndWeatherMapResponseServerHealthReportCoverageMapResponseEphemerisResponsePassPredictionResponseMonitorAck
Catalog CatalogListRequestCatalogFetchCatalogUploadCatalogDeleteCatalogListCatalogEntryCatalogUploadResultCatalogDeleteResult
Moderation KickRequestIpModerationRequestBroadcastBannerKickResultIpModerationResultStationImpactList
Archive Protocol v24 reference