Operator API

URnetwork API: Authentication

Version 1.0.0 · 201 operations across 14 groups.OpenAPI spec ↗

URnetwork implements the UR protocol operator spec in full. UR protocol ↗

Authentication

22 operations

POST/auth/login

PublicauthLogin

Start a login for a user authority. The user authority may be:

  • email
  • phone number
  • Apple JWT
  • Google JWT
  • a wallet (wallet_auth): a Solana or Bittensor signature over the single-use challenge from /auth/wallet-challenge. A bound wallet returns network.by_jwt; an unbound wallet echoes wallet_auth and the client continues to /auth/network-create with a fresh challenge.
  • a seedphrase

A well-formed wallet signature that does not verify for wallet_address over the challenge (in practice the wallet signed with another account than the address entered) is refused with error.code signature_mismatch when the request sets result_errors, as a 200. Without it, that refusal is a 401 with a plain text body. Every other wallet refusal keeps its HTTP status.

show schema
user_auth
string

email or phone number

auth_jwt
string
auth_jwt_type
string
enum: apple, google
wallet_auth
WalletAuthArgs
wallet_address
string

Solana base58 public key, or Bittensor ss58 address (prefix 42, checksum verified)

wallet_message
string

The message_template issued by /auth/wallet-challenge, unmodified (no <Bytes> wrapper, LF line endings)

wallet_signature
string

Solana: base64 ed25519 signature over the message. Bittensor: hex of the 64-byte sr25519 signature (with or without 0x) made in the substrate signing context, over the raw or <Bytes>-wrapped message

blockchain
string

solana (default) or tao/bittensor; case-insensitive

enum: solana, tao, bittensor
wallet_nonce
string

The single-use nonce from /auth/wallet-nonce, embedded in wallet_message and echoed here. Optional during the client rollout; enforced whenever it is present. The newer /auth/wallet-challenge flow supersedes it.

seedphrase
string

The recovery seedphrase, for a seedphrase login.

result_errors
boolean

Report a refusal that has a code (signature_mismatch) in the result error with a 200, instead of a 401. New clients set this.

1 status · show schema
200
user_name
string
user_auth
string

email or phone number

auth_allowed
array<string>

The sign-in methods on file for this user authority

array
string
enum: password, email, phone, apple, google, solana, bittensor, seedphrase
error
object
suggested_user_auth
string
enum: password, apple, google
code
string

A stable code for a refusal that has one, so a client can pick a localized message. Only with result_errors. signature_mismatch: the wallet signature is well formed but does not verify for wallet_address over the challenge. In practice the wallet signed with another account than the address entered (an sr25519 signature cannot be recovered to the key that made it, so the server cannot name that account), or signed other text. Sign the issued challenge with the entered address. The challenge is not used up. Absent for every other refusal; clients fall back to message for an absent or unknown code.

message
string
network
object
by_jwt
string
wallet_auth
WalletAuthArgs
wallet_address
string

Solana base58 public key, or Bittensor ss58 address (prefix 42, checksum verified)

wallet_message
string

The message_template issued by /auth/wallet-challenge, unmodified (no <Bytes> wrapper, LF line endings)

wallet_signature
string

Solana: base64 ed25519 signature over the message. Bittensor: hex of the 64-byte sr25519 signature (with or without 0x) made in the substrate signing context, over the raw or <Bytes>-wrapped message

blockchain
string

solana (default) or tao/bittensor; case-insensitive

enum: solana, tao, bittensor
wallet_nonce
string

The single-use nonce from /auth/wallet-nonce, embedded in wallet_message and echoed here. Optional during the client rollout; enforced whenever it is present. The newer /auth/wallet-challenge flow supersedes it.

POST/auth/wallet-challenge

PublicauthWalletChallenge

Issue a single-use wallet sign-in challenge. The wallet signs the returned message_template byte for byte; the signature is then submitted as wallet_auth to /auth/login, /auth/network-create or /auth/add-auth, each of which consumes one challenge. The message is exactly three LF-separated lines: Sign in to URnetwork, Challenge: <challenge> and Timestamp: <unix seconds>; the server rejects any other text (400 invalid message format), a challenge that is unknown, expired (5 minutes), already used, issued for another blockchain or address, or whose timestamp differs from the issued one. Bittensor (TAO) wallets sign with sr25519 in the substrate context; both the raw text and the polkadot-js <Bytes>…</Bytes>-wrapped form of the signature verify, and the unwrapped text is what the client submits. Rate limited per client address. See server/BITTENSOR-LOGIN.md.

show schema
blockchain
string

solana (default) or tao/bittensor; case-insensitive

enum: solana, tao, bittensor
wallet_address
string

Optional. Solana base58 public key or Bittensor ss58 address; when given, the challenge is bound to it.

1 status · show schema
200
challenge
string

32 random bytes, base64url; embedded in message_template

timestamp
integer

unix seconds the challenge was issued; embedded in message_template

expires_in
integer

seconds until the challenge expires (300)

message_template
string

The exact text to sign: Sign in to URnetwork Challenge: <challenge> Timestamp: <timestamp>

error
object
message
string

POST/auth/wallet-nonce

PublicauthWalletNonce

Issue a fresh single-use, short-lived (5 minute) nonce for wallet login. The client embeds the returned nonce in the message it signs and echoes it back as wallet_auth.wallet_nonce; the server validates and consumes it in the same transaction as the login, so a captured (message, signature) pair cannot be replayed. The nonce is optional during the client rollout and enforced whenever it is present.

This is the older, narrower anti-replay token. /auth/wallet-challenge supersedes it: that one returns the exact message text to sign and binds the challenge to a blockchain and an address.

1 status · show schema
200
nonce
string

Single-use, expires in 5 minutes. Embed it in the signed message and echo it as wallet_auth.wallet_nonce.

POST/auth/login-with-password

PublicauthLoginWithPassword

Password login for email and phone number.

show schema
user_auth
string

email or phone number

password
string
verify_otp_numeric
boolean
1 status · show schema
200
verification_required
object
user_auth
string

email or phone number

send_error
AuthVerifySendError

Present when the verification code was not sent. Clients must not say a code was sent, and show this instead.

code
string

verify_send_failed: the email or SMS send failed. verify_rate_limited: too many attempts; see retry_after_seconds. Clients treat an unknown code as a code that was not sent.

message
string
retry_after_seconds
integer

For verify_rate_limited, seconds until a new code can be requested.

network
object
by_jwt
string
name
string
error
object
message
string

POST/auth/verify

PublicauthVerify

Verify ownership of email or phone number.

show schema
user_auth
string

email or phone number

verify_code
string
1 status · show schema
200
network
object
by_jwt
string
error
object
message
string

GET/auth/refresh

Bearer authauthRefreshToken

Refresh the client JWT (by_jwt) for the caller's client. Requires a client JWT (from /network/auth-client). A network JWT (the network's sign-in token, with no client) is refused here with a result error; renew it with POST /auth/network-refresh.

The refreshed JWT keeps the create time of the JWT it refreshes, so a password reset that expires one expires the other.

1 status · show schema
200
by_jwt
string

The refreshed client JWT.

error
object
message
string

POST/auth/network-refresh

Bearer authauthNetworkRefresh

Renew the network's sign-in token: a network JWT, with no client. Every JWT lives 30 days. The result is a new network JWT for the same network and user, with a new expiration, the network's current name and its current Pro state. It keeps the create time of the JWT it renews, so a password reset that expires one expires the other.

Renew a network JWT before it expires. While the server still accepts expired JWTs, an expired network JWT renews too; once it does not, the user signs in again.

A client JWT (by_client_jwt, from POST /network/auth-client) is refused with 403, because a client JWT never obtains a network JWT; refresh a client JWT with GET /auth/refresh. An API key does not expire, and is refused with a result error.

3 statuses · show schema
200
by_jwt
string

The renewed network JWT.

error
object
message
string
401
No body
403
No body

POST/auth/verify-send

PublicauthVerifySend

Send verification code to email or phone number.

With result_errors set, a code that was not sent is reported in the result error (verify_send_failed, or verify_rate_limited with retry_after_seconds). Without it, a rate limit is a 429 and a failed send is a 502, both with a plain text body. Clients must not report a code as sent unless the response is a 200 without error.

show schema
user_auth
string

email or phone number

use_numeric
boolean

optionally create a numeric 6 digit code instead of the default 8 digit

result_errors
boolean

Report a code that was not sent in the result error with a 200, instead of a 429 or 502. New clients set this.

3 statuses · show schema
200
user_auth
string

email or phone number

error
AuthVerifySendError

Present when the code was not sent (only with result_errors).

code
string

verify_send_failed: the email or SMS send failed. verify_rate_limited: too many attempts; see retry_after_seconds. Clients treat an unknown code as a code that was not sent.

message
string
retry_after_seconds
integer

For verify_rate_limited, seconds until a new code can be requested.

429
No body
502
No body

POST/auth/password-reset

PublicauthPasswordReset

Send password reset code to email or phone number.

With result_errors set, a reset code that was not sent is reported in the result error (verify_send_failed, or verify_rate_limited with retry_after_seconds). Without it, a rate limit is a 429 and a failed send is a 502, both with a plain text body. Clients must not report a reset code as sent unless the response is a 200 without error.

show schema
user_auth
string

email or phone number

result_errors
boolean

Report a reset code that was not sent in the result error with a 200, instead of a 429 or 502. New clients set this.

3 statuses · show schema
200
user_auth
string

email or phone number

error
AuthVerifySendError

Present when the reset code was not sent (only with result_errors).

code
string

verify_send_failed: the email or SMS send failed. verify_rate_limited: too many attempts; see retry_after_seconds. Clients treat an unknown code as a code that was not sent.

message
string
retry_after_seconds
integer

For verify_rate_limited, seconds until a new code can be requested.

429
No body
502
No body

POST/auth/password-set

PublicauthPasswordSet

Change password.

show schema
reset_code
string
password
string
1 status · show schema
200
empty object

POST/auth/network-check

PublicauthNetworkCheck

Check if the network name is available. The name is normalized before it is checked: trimmed, lower-cased, and spaces replaced with dashes. The normalized name must satisfy:

  • at least 5 characters
  • at most 50 characters
  • only lowercase letters, digits and dashes and must not be within one edit of an existing network name.
show schema
network_name
string
1 status · show schema
200
available
boolean

POST/auth/network-create

PublicauthNetworkCreate

Create a new network. A user authority can be associated with at most one network.

A well-formed wallet signature that does not verify for wallet_address over the challenge (in practice the wallet signed with another account than the address entered) is refused with a 401. When the request sets result_errors, the 401's body is the JSON result with error.code signature_mismatch; without it, the body is the plain text message. A 2xx is always a created network or its pending state, never a refusal. Every other refusal keeps its HTTP status and plain text body.

show schema
user_name
string
user_auth
string

email or phone number

auth_jwt
string
auth_jwt_type
string
enum: apple, google
password
string
network_name
string
terms
boolean

user consent to accept terms of service

verify_use_numeric
boolean
wallet_auth
WalletAuthArgs
wallet_address
string

Solana base58 public key, or Bittensor ss58 address (prefix 42, checksum verified)

wallet_message
string

The message_template issued by /auth/wallet-challenge, unmodified (no <Bytes> wrapper, LF line endings)

wallet_signature
string

Solana: base64 ed25519 signature over the message. Bittensor: hex of the 64-byte sr25519 signature (with or without 0x) made in the substrate signing context, over the raw or <Bytes>-wrapped message

blockchain
string

solana (default) or tao/bittensor; case-insensitive

enum: solana, tao, bittensor
wallet_nonce
string

The single-use nonce from /auth/wallet-nonce, embedded in wallet_message and echoed here. Optional during the client rollout; enforced whenever it is present. The newer /auth/wallet-challenge flow supersedes it.

referral_code
string

Optional referral code of the network that referred this new network.

balance_code
string

Optional balance code to add transfer balance to the new network.

product_updates
boolean

The sign-up form's "Periodic product updates" line. Absent = true (the line ships ticked). false turns the product-updates preference off from the first moment: no onboarding campaign mail is ever sent. Honored on every create path, including sign-ups that complete through /auth/verify. The server records a signup.optout_changed event with the value.

result_errors
boolean

Answer a refusal that has a code (signature_mismatch) with the result as the JSON body of its 401, instead of the plain text message. New clients set this.

3 statuses · show schema
200
network
object
by_jwt
string
network_id
string

uuid

network_name
string
is_pro
boolean
user_auth
string

email or phone number

seedphrase
string

The generated recovery seedphrase, returned once on the seedphrase sign-up path. It is never retrievable again.

is_pro
boolean
verification_required
object
user_auth
string

email or phone number

send_error
AuthVerifySendError

Present when the verification code was not sent. Clients must not say a code was sent, and show this instead.

code
string

verify_send_failed: the email or SMS send failed. verify_rate_limited: too many attempts; see retry_after_seconds. Clients treat an unknown code as a code that was not sent.

message
string
retry_after_seconds
integer

For verify_rate_limited, seconds until a new code can be requested.

error
object
code
string

A stable code for a refusal that has one, so a client can pick a localized message. Only with result_errors, in the body of the refusal's 401. signature_mismatch: the wallet signature is well formed but does not verify for wallet_address over the challenge. In practice the wallet signed with another account than the address entered, or signed other text. Sign the issued challenge with the entered address. The challenge is not used up. Clients fall back to message for an absent or unknown code.

message
string
400
No body
401
network
object
by_jwt
string
network_id
string

uuid

network_name
string
is_pro
boolean
user_auth
string

email or phone number

seedphrase
string

The generated recovery seedphrase, returned once on the seedphrase sign-up path. It is never retrievable again.

is_pro
boolean
verification_required
object
user_auth
string

email or phone number

send_error
AuthVerifySendError

Present when the verification code was not sent. Clients must not say a code was sent, and show this instead.

code
string

verify_send_failed: the email or SMS send failed. verify_rate_limited: too many attempts; see retry_after_seconds. Clients treat an unknown code as a code that was not sent.

message
string
retry_after_seconds
integer

For verify_rate_limited, seconds until a new code can be requested.

error
object
code
string

A stable code for a refusal that has one, so a client can pick a localized message. Only with result_errors, in the body of the refusal's 401. signature_mismatch: the wallet signature is well formed but does not verify for wallet_address over the challenge. In practice the wallet signed with another account than the address entered, or signed other text. Sign the issued challenge with the entered address. The challenge is not used up. Clients fall back to message for an absent or unknown code.

message
string

POST/auth/network-delete

Bearer authauthNetworkDelete

Delete network

2 statuses · show schema
200
error
object
message
string
403
No body

POST/auth/code-create

Bearer authauthCodeCreate

Create a limited use code (auth code) to share authentication with connected apps and tools. The code is tied to the caller session, and will be expired with any of the caller's sessions. This is a subset of an OAuth flow. For the full OAuth 2.1 / OpenID Connect flow see the /oauth routes.

show schema
duration_minutes
number
uses
integer
roles
array<string>

Optional. Identity roles carried by logins minted from this code. Only a network session may set these. The values have no meaning to the network.

array
string
principal
string

Optional. Identity principal carried by logins minted from this code. Only a network session may set this.

2 statuses · show schema
200
auth_code
string
duration_minutes
number
uses
integer
error
object
auth_code_limit_exceeded
boolean
message
string
403
No body

GET/auth/apple/callback

PublicauthAppleCallbackGet
state(query)
string
required
id_token(query)
string
code(query)
string
user(query)
string
error(query)
string
2 statuses · show schema
302
No body
400
No body

POST/auth/apple/callback

PublicauthAppleCallback

Apple has no SDK for android, windows or linux, so those apps open Apple's authorize page in the system browser (a Custom Tab on android) with client_id = the Apple Services ID, redirect_uri = this endpoint, response_type=code id_token, response_mode=form_post, scope=name email, and a fresh state and nonce per attempt. Apple posts the result here as a form, and this endpoint answers 302 to the app's own scheme so the browser hands control back to the app:

Text
<scheme>://oauth/apple?state=…&id_token=…&code=…&user=…
<scheme>://oauth/apple?state=…&error=…

user is the JSON name/email Apple sends with the first authorization only. Nothing is stored or verified here: the app checks that state is the attempt it started and that the identity token's nonce claim is the one it minted, then signs in with the token through POST /auth/login (auth_jwt_type: apple), which verifies the signature and the audience.

state is opaque except for one optional claim: when it is the base64url encoding of a JSON object with a platform key, that key picks the scheme (android → ur://, windows and linux → urnetwork://); without it the android scheme is used. GET with the same parameters as a query behaves the same way (manual testing).

show schema
state
string

The attempt's state, echoed by Apple untouched.

code
string

Apple's authorization code (unused by the apps, passed through).

id_token
string

Apple's identity token (a JWT carrying the attempt's nonce).

user
string

JSON with the user's name and email, first authorization only.

error
string

Apple's error, e.g. user_cancelled_authorize.

2 statuses · show schema
302
No body
400
No body

GET/auth/google/callback

PublicauthGoogleCallback

Windows and Linux have no native Google sign-in, so those apps open Google's authorize page in the system browser with client_id = the ur.io web sign-in client, redirect_uri = this endpoint, response_type=code, scope=openid email profile, prompt=select_account, and a fresh state and nonce per attempt (android signs in with play services and ur.io in the page; neither uses this). Google redirects the browser here with the authorization code; this endpoint exchanges it at Google's token endpoint with the web client's secret and answers 302 to the app's own scheme so the browser hands control back to the app:

Text
<scheme>://oauth/google?state=…&id_token=…
<scheme>://oauth/google?state=…&error=…

Nothing is stored. The app checks that state is the attempt it started and that the identity token's nonce claim is the one it minted, then signs in with the token through POST /auth/login (auth_jwt_type: google), which verifies the signature and the audience.

state is opaque except for the optional platform claim shared with the Apple callback: base64url JSON with a platform key picks the scheme (android → ur://, windows and linux → urnetwork://); without it the android scheme is used. Google's own error (e.g. access_denied) is passed through; a failed exchange or a missing client configuration comes back as error too (not_configured when the server has no Google sign-in client configured).

state(query)
string

The attempt's state, echoed by Google untouched.

required
code(query)
string

Google's authorization code, exchanged here for the identity token.

error(query)
string

Google's error, e.g. access_denied.

3 statuses · show schema
302
No body
400
No body
405
No body

POST/auth/code-login

PublicauthCodeLogin

Authenticate with an auth code. Redemption creates an independent sign-in. An unredeemed code is invalidated when its creator's session is revoked; a completed redemption is governed by its own session thereafter. Reuse the same request_id after a lost response. Reusing it with another code returns a generic request_id_conflict without exposing the prior redemption. This is a subset of an OAuth flow.

show schema
auth_code
string
request_id
string,null (uuid)

Stable redemption identity reused after a lost response; redemption creates an independent sign-in.

2 statuses · show schema
200
by_jwt
string
error
object
message
string
409
No body

POST/auth/add-auth

Bearer authauthAddAuth

Add an authentication method (email/phone + password, SSO, or wallet) to the calling user. A wallet (wallet_auth, Solana or Bittensor) must sign the single-use challenge from /auth/wallet-challenge, the same proof as wallet login; one wallet address binds to one user. Subject to the account action rate limit.

Refusals come back in the result error as a 200. A well-formed wallet signature that does not verify for wallet_address over the challenge (in practice the wallet signed with another account than the address entered) has error.code signature_mismatch.

show schema
user_auth
string

email or phone number

auth_jwt
string
auth_jwt_type
string
enum: apple, google
password
string
wallet_auth
WalletAuthArgs
wallet_address
string

Solana base58 public key, or Bittensor ss58 address (prefix 42, checksum verified)

wallet_message
string

The message_template issued by /auth/wallet-challenge, unmodified (no <Bytes> wrapper, LF line endings)

wallet_signature
string

Solana: base64 ed25519 signature over the message. Bittensor: hex of the 64-byte sr25519 signature (with or without 0x) made in the substrate signing context, over the raw or <Bytes>-wrapped message

blockchain
string

solana (default) or tao/bittensor; case-insensitive

enum: solana, tao, bittensor
wallet_nonce
string

The single-use nonce from /auth/wallet-nonce, embedded in wallet_message and echoed here. Optional during the client rollout; enforced whenever it is present. The newer /auth/wallet-challenge flow supersedes it.

2 statuses · show schema
200
error
object
code
string

A stable code for a refusal that has one, so a client can pick a localized message. signature_mismatch: the wallet signature is well formed but does not verify for wallet_address over the challenge. In practice the wallet signed with another account than the address entered, or signed other text. Sign the issued challenge with the entered address. The challenge is not used up. Absent from older servers and for every other refusal; clients fall back to message for an absent or unknown code.

message
string
403
No body

POST/auth/remove-auth

Bearer authauthRemoveAuth

Remove an authentication method from the calling user. The last remaining method cannot be removed.

show schema
auth_type
string

The sign-in method to remove; the last remaining method cannot be removed

enum: email, phone, apple, google, solana, bittensor, seedphrase
2 statuses · show schema
200
error
object
message
string
403
No body

POST/auth/generate-seedphrase

Bearer authauthGenerateSeedphrase

Generate a recovery seedphrase for the calling user. Fails if a seedphrase auth already exists; see /auth/regenerate-seedphrase.

2 statuses · show schema
200
seedphrase
string
error
object
message
string
403
No body

POST/auth/regenerate-seedphrase

Bearer authauthRegenerateSeedphrase

Replace the calling user's recovery seedphrase with a new one, invalidating the previous seedphrase.

2 statuses · show schema
200
seedphrase
string
error
object
message
string
403
No body