Error catalogue
Every cause, in the order a login runs
57 causes smart-doctor can report: what each means and how to fix it. Search for the error code or the exact message your server sent.
No cause matches. Try the error code alone, like invalid_client, or run npx smart-doctor explain with the full message.
By error code
The standard OAuth codes, and what usually sits behind each.
Reaching the server
7 causesBefore any login: the network, the FHIR base URL and the token endpoint.
- networkCannot reach the serverThe request never got an HTTP answer (DNS, firewall, VPN, proxy, or TLS problem).
- wrong_baseFHIR base URL is wrongNeither .well-known/smart-configuration nor /metadata exposed a token endpoint.
- wrong_token_urlToken URL is wrongThe token endpoint answered 404/405 or HTML instead of an OAuth JSON response.
- token_url_differsYou are using a token URL the server does not advertiseYour --token-url differs from the server’s advertised token_endpoint. aud must equal the endpoint you post to, and some servers only accept the advertised one.
- rate_limitedRate limitedThe server answered 429 Too Many Requests.
- server_errorServer-side errorThe token endpoint returned a 5xx error; this is usually not your request.
- unrecognised_errorThe server’s error is not one smart-doctor recognisesThe server rejected the request with an error smart-doctor has no rule for, so it cannot rank causes. The server’s own wording is the best lead.
Client secrets
6 causesConfidential clients that authenticate with a client secret.
- auth_methodServer does not support this client authentication methodYour --auth method is not in the server’s token_endpoint_auth_methods_supported.
- secret_missingNo client secret providedSecret-based client authentication was selected but no secret was available.
- secret_whitespaceClient secret has stray whitespaceLeading or trailing spaces/newlines from copy-paste make an otherwise correct secret fail.
- secret_invalidClient secret rejectedThe secret is wrong for this client_id: rotated or expired, from another environment, or for a different app.
- secret_method_requiredServer wants the client secret sent another wayThe server named the client authentication it requires (for example HTTP Basic). The secret itself may well be right.
- secret_methodBasic vs post mismatchThe server may only accept the secret in the Authorization header (client_secret_basic) or only in the body (client_secret_post), or may decode Basic credentials differently.
Keys and JWKS
9 causesYour signing key, the public JWKS the server reads, and how the two match.
- jwks_private_leakYour public JWKS is leaking your PRIVATE keyThe JWKS URL publishes private key fields (e.g. "d"). Anyone can sign as your app.
- kid_missingNo kid in the assertion headerWithout a kid the server cannot pick your key from the JWKS.
- kid_unknownYour kid is not in the JWKS the server knows aboutThe server looks up the kid from your header in your registered JWKS and does not find it.
- key_mismatchSigning key does not match the published public keyThe key under your kid is not the public half of the private key you sign with, so the signature fails.
- signatureSignature could not be verifiedThe server found a key but the signature did not verify against it (wrong key, wrong alg, or a mangled token).
- jwks_fetchThe server cannot fetch your JWKSYour JWKS URL is unreachable, not HTTPS, redirects, or is not valid JSON — the EHR fetches it from the public internet.
- jwks_no_algNo key for your algorithm in the registered JWKSThe server read your registered JWKS but found no key for the algorithm you sign with (for example you sign RS384 but registered an EC key). You are probably signing with a different key than the one registered.
- alg_unsupportedSigning algorithm not acceptedThe server does not list your alg in token_endpoint_auth_signing_alg_values_supported, or the alg does not match the key.
- registered_key_unknownThe public key on file may not be this keyNo --jwks-url was given, so smart-doctor cannot compare the key the server has with your private key.
The signed client assertion
6 causesThe private_key_jwt the server checks on every token request.
- aud_mismatchaud does not exactly match the token endpointThe assertion’s aud must be the token endpoint URL, character for character (trailing slash, case, http/https and path all matter).
- clock_skewClock skew between you and the serverAssertions live at most 5 minutes; a skewed clock makes them look expired or not yet valid.
- assertion_timeAssertion expired, too long-lived, or not yet validexp must be in the future and at most 5 minutes out; iat/nbf must not be in the future.
- jti_reusejti reusedServers reject an assertion whose jti they have already seen.
- iss_subiss/sub is not your client_idBoth iss and sub must equal the client_id registered for this environment.
- generic_client_authGeneric client authentication failureThe server reports a generic failure that covers many assertion problems.
Client registration
4 causesWhat the server knows about your client, which you cannot see from outside.
- client_unknownWrong client_id for this environmentThe server does not recognise this client_id (non-production vs production IDs, or a typo).
- client_inactiveClient exists but is not enabled/authorisedThe client is registered but disabled, not activated for this organisation, or not allowed to use client_credentials.
- server_side_registrationServer-side registration does not match (what we cannot see)Every check we can do locally passes, so the remaining causes live in the server’s registration: wrong environment client_id, a different public key on file, a key/JWKS change not picked up yet, or the app not activated.
- grant_typeclient_credentials grant not allowedThe server rejected the grant type for this client or endpoint.
Scopes
3 causesWhat you ask for versus what the registration allows.
- scopeRequested scopes not allowedThe client is not registered for one or more requested scopes, or the scope syntax is not what this server expects.
- scope_syntaxScope syntax version may not match the serverYou used SMART v2 scope syntax (.rs/.cruds) but the server only advertises v1, or vice versa.
- scope_partialFewer scopes granted than requestedThe token was issued but without some requested scopes; calls needing them will get 403.
Sign-in at the EHR
13 causesThe authorization request, the redirect back, and its state.
- redirect_not_registeredThe EHR never sent the browser back: redirect URI not registered exactlyWhen redirect_uri does not match a registered value character for character, EHRs show their own error page and never redirect. Scheme, host (localhost vs 127.0.0.1), port, path and trailing slash all count.
- signin_not_completedSign-in was not completedThe browser was not used to finish signing in (closed tab, wrong account, login failure, or MFA timeout).
- loopback_portCannot listen on the redirect portAnother program is using the port in your loopback redirect URI, or it needs admin rights.
- access_deniedAccess denied at sign-inThe user declined, or this user/organisation is not allowed to use the app.
- authorize_scopeScopes rejected at sign-inOne or more requested scopes are not allowed for this app at this EHR.
- authorize_clientApp not allowed to sign in this wayunauthorized_client: the app is not enabled for this flow, not activated at this organisation, or registered as a different client type.
- authorize_requestAuthorization request rejectedThe EHR refused the sign-in request itself, before anyone signed in. Its error description is the best clue; common culprits are aud (must equal the FHIR base URL), PKCE, or a parameter the EHR does not expect.
- authorize_redirectRedirect URI rejected at sign-inThe EHR says the redirect_uri is not registered for this app. It must match a registered value character for character: scheme, host (localhost vs 127.0.0.1), port, path and trailing slash all count.
- authorize_audaud rejected at sign-inSMART EHRs require aud to be the FHIR base URL the app talks to, exactly as the EHR expects it (trailing slash, http vs https, version path).
- state_mismatchstate does not matchThe pasted callback came from a different sign-in attempt (another tab or an earlier run). smart-doctor refuses it, as a real app must.
- iss_mismatchCallback came from a different serverThe iss parameter names another authorization server — a mix-up an app must reject.
- bad_pasteThe pasted URL is incompleteThe callback URL needs its query string (code=… and state=…).
- s256_unsupportedServer does not advertise PKCE S256SMART App Launch v2 requires S256. The server may be SMART v1 only or misconfigured.
Code exchange
4 causesTurning the authorization code into tokens.
- code_consumedAuthorization code already usedCodes are single-use. If the company’s redirect page is a live app, it probably exchanged the code itself before you pasted it.
- code_expiredAuthorization code expiredCodes are short-lived (often 1–10 minutes).
- redirect_mismatch_tokenredirect_uri differs between sign-in and token exchangeThe token request must send the identical redirect_uri used in the authorization request.
- pkce_failedPKCE verification failedThe code_verifier does not hash to the code_challenge sent at sign-in.
Tokens, context and access
5 causesWhat came back: patient context, id_token, refresh token, and the first FHIR call.
- probe_failedToken works, but the FHIR call was refusedA token was issued, yet the probe request was rejected (401/403).
- missing_patientNo patient context returnedlaunch/patient was requested but no patient came back — the scope may not have been granted, or no patient was chosen.
- id_token_problemid_token problemopenid was requested but the id_token is missing, for another audience, or does not verify.
- refresh_problemRefresh tokens not workingoffline_access/online_access was requested but no usable refresh token was issued.
- refresh_rejectedRefresh token rejectedA refresh token was issued, but using it failed: it may have expired or been revoked, be single-use (rotated on each refresh), or the server may expect client authentication on refresh.