BeyondDimension/SteamTools · warning · ApplicationException
Import only supports otpauth://totp/ or otpauth://hotp/
Error message
Import only supports otpauth://totp/ or otpauth://hotp/
What it means
Thrown during authenticator import when the URI scheme is otpauth but the host (the OTP type segment) is neither 'totp' nor 'hotp'. The importer supports only time-based and HMAC-based counter one-time passwords; any other type label in otpauth://<type>/ is rejected.
Source
Thrown at src/BD.WTTS.Client.Plugins.Authenticator/UI/ViewModels/AuthenticatorImportPageViewModel.cs:272
var qm = line.IndexOf("?", StringComparison.Ordinal);
if (hash != -1 && hash < qm)
{
line = $"{line.Substring(0, hash)}%23{line[(hash + 1)..]}";
}
// parse and validate URI
var uri = new Uri(line);
// we only support "otpauth"
if (uri.Scheme != "otpauth")
{
throw new ApplicationException("Import only supports otpauth://");
}
// we only support totp (not hotp)
if (uri.Host != "totp" && uri.Host != "hotp")
{
throw new ApplicationException("Import only supports otpauth://totp/ or otpauth://hotp/");
}
// get the label and optional issuer
string issuer = string.Empty;
string label = string.IsNullOrEmpty(uri.LocalPath) == false
? uri.LocalPath[1..]
: string.Empty; // skip past initial /
int p = label.IndexOf(":", StringComparison.Ordinal);
if (p != -1)
{
issuer = label.Substring(0, p);
label = label[(p + 1)..];
}
// + aren't decoded
label = label.Replace("+", " ");
var query = HttpUtility.ParseQueryString(uri.Query);View on GitHub (pinned to c16ffa08e0)
Solutions
- Re-export the entry as otpauth://totp/ or otpauth://hotp/ from the source authenticator.
- For Steam entries, import via the dedicated Steam Guard import flow instead of the otpauth parser.
- Manually correct the type segment to 'totp' (most secrets are TOTP-compatible).
- Reject the entry in the UI with a clear message listing the supported types.
Example fix
// before
if (uri.Host != "totp" && uri.Host != "hotp")
throw new ApplicationException("Import only supports otpauth://totp/ or otpauth://hotp/");
// after: tolerant, case-insensitive check with guidance
var type = uri.Host.ToLowerInvariant();
if (type != "totp" && type != "hotp")
throw new ArgumentException(string.Format(Strings.Import_UnsupportedOtpType, uri.Host)); Defensive patterns
Strategy: validation
Validate before calling
static bool IsSupportedOtpType(Uri uri)
=> uri.Host.Equals("totp", StringComparison.OrdinalIgnoreCase)
|| uri.Host.Equals("hotp", StringComparison.OrdinalIgnoreCase);
if (!IsSupportedOtpType(uri))
errors.Add(($"Line {n}: otpauth type '{uri.Host}' not supported (use totp or hotp).", line)); Type guard
bool IsTotpOrHotp(Uri u) => u.Host is "totp" or "hotp";
Try / catch
try { ParseAuthenticator(line); }
catch (ApplicationException ex) when (ex.Message.Contains("otpauth://totp/ or otpauth://hotp/"))
{
importErrors.Add(($"Unsupported OTP type (line {n}).", line));
continue;
} Prevention
- Validate the type segment case-insensitively and surface the allowed set to the user.
- Route Steam-style entries to the dedicated Steam Guard importer.
- Show a per-line error list during bulk import instead of aborting on the first bad entry.
- Normalize common type aliases before rejecting (e.g. 'totp' typos).
When it happens
Trigger: Parsing otpauth:// URIs where the authority segment is something other than totp or hotp — e.g. otpauth://steam/..., a custom type, or a malformed URI whose 'host' is empty.
Common situations: Steam-style URIs using a non-standard type label; entries exported by a tool that invented its own type; URI was truncated so the type segment is missing; case/encoding issues that shifted the host.
Related errors
- HOTP authenticator should have a counter
- Import only supports otpauth://
- Authenticator does not contain secret
- Battle.net Authenticator does not have a serial
- Invalid serial for Battle.net Authenticator
AI-assisted analysis of BeyondDimension/SteamTools@c16ffa08e0 (2026-08-13).
Data as JSON: /api/errors/8c3b483803a74d85.
Report an issue: GitHub.