Error Handling in SDKs
Every SDK call that reaches the Descope servers can fail. When it does, the SDK reports a Descope error code (for example E061102), a human-readable description, and sometimes a more specific message. You can look up any code on the Common Errors page.
Note
For a list of common error codes and what they mean, see the Common Errors reference.
Checking for Errors by SDK
Each tab shows how one SDK types its errors and which codes it exposes, plus how to:
- Check for a specific error code
- Check for a category, such as unauthorized or rate limited
- Read the code and description off an error
The Node.js SDK never throws on a failed request. Every call returns an SdkResponse<T> — check ok before using data, and read the details off error.
{
ok: boolean; // false when the request failed
code?: number; // HTTP status code (e.g. 401, 429)
error?: {
errorCode: string; // Descope error code, e.g. "E061102"
errorDescription: string; // human-readable summary
errorMessage?: string; // extra context (not always present)
};
data?: T; // present when ok === true
}Import the client and the typed error-code map (DescopeErrors):
import DescopeClient from '@descope/node-sdk';
const descopeClient = DescopeClient({ projectId: '__ProjectID__' });
const { DescopeErrors } = DescopeClient;
const resp = await descopeClient.otp.verify.email(loginId, code);
if (!resp.ok) {
// Read the code + description off the response
console.log(resp.error?.errorCode); // "E061103"
console.log(resp.error?.errorDescription);
console.log(resp.code); // HTTP status, e.g. 401
// Check for a specific typed error code
if (resp.error?.errorCode === DescopeErrors.tooManyOTPAttempts) {
// too many wrong OTP attempts (E061103)
}
// Check for a category using the HTTP status code
if (resp.code === 429) {
// rate limited
}
} else {
// Success — use resp.data
const { sessionJwt } = resp.data;
}Codes exposed on DescopeErrors:
| Constant | Code |
|---|---|
DescopeErrors.badRequest | E011001 |
DescopeErrors.missingArguments | E011002 |
DescopeErrors.invalidRequest | E011003 |
DescopeErrors.invalidArguments | E011004 |
DescopeErrors.wrongOTPCode | E061102 |
DescopeErrors.tooManyOTPAttempts | E061103 |
DescopeErrors.enchantedLinkPending | E062503 |
DescopeErrors.userNotFound | E062108 |
The Go SDK returns a standard error whose concrete type is *descope.Error. Use the package helpers to inspect it.
type Error struct {
Code string // e.g. "E061102"
Description string // human-readable summary
Message string // extra context (not always present)
Info map[string]any // extra metadata (HTTP status, Retry-After)
}result, err := descopeClient.Auth.OTP().VerifyCode(ctx, descope.MethodEmail, loginID, code, w)
// 1. Check for a specific error code
if descope.IsError(err, "E061102") {
// wrong OTP code
}
// 2. Or match a predefined error with the standard errors.Is (requires: import "errors")
if errors.Is(err, descope.ErrInvalidOneTimeCode) {
// wrong OTP code (E061102)
}
// 3. Check for a category (400 / 401 / 403 / 404)
if descope.IsUnauthorizedError(err) {
// unauthorized
}
// 4. Read the code and description directly
if de := descope.AsError(err); de != nil {
log.Printf("failed: [%s] %s", de.Code, de.Description)
}Rate-limit errors carry a Retry-After value in the Info map:
if de := descope.AsError(err, descope.ErrRateLimitExceeded.Code); de != nil {
retryAfter := de.Info[descope.ErrorInfoKeys.RateLimitExceededRetryAfter] // seconds
}Predefined error variables (each is a *descope.Error with a fixed Code):
| Variable | Code |
|---|---|
descope.ErrBadRequest | E011001 |
descope.ErrMissingArguments | E011002 |
descope.ErrValidationFailure | E011003 |
descope.ErrInvalidArguments | E011004 |
descope.ErrInvalidOneTimeCode | E061102 |
descope.ErrUserAlreadyExists | E062107 |
descope.ErrEnchantedLinkUnauthorized | E062503 |
descope.ErrPasswordExpired | E062909 |
descope.ErrTokenExpiredByLoggedOut | E064001 |
descope.ErrNOTPUnauthorized | E066103 |
descope.ErrManagementUserNotFound | E112102 |
descope.ErrRateLimitExceeded | E130429 |
Client-side errors (SDK/config, not from the server) use G-prefixed codes, such as descope.ErrMissingProjectID (G010001), descope.ErrInvalidToken (G030002), and descope.ErrRefreshToken (G030003).
The .NET SDK throws DescopeException when a request fails. It exposes ErrorCode, ErrorDescription, and ErrorMessage, and its Message is formatted as [code]: description (message).
public class DescopeException : ApplicationException
{
public string? ErrorCode { get; } // "E062504"
public string? ErrorDescription { get; } // "Token expired"
public string? ErrorMessage { get; } // "Failed to load magic link token"
// Message => "[E062504]: Token expired (Failed to load magic link token)"
}using Descope;
try
{
var response = await client.Auth.V1.Auth.Magiclink.Verify.PostAsync(
new VerifyMagicLinkRequest { Token = token });
}
catch (DescopeException e)
{
// Read the details off the exception
Console.WriteLine(e.ErrorCode); // "E062504"
Console.WriteLine(e.ErrorDescription); // "Token expired"
Console.WriteLine(e.Message); // "[E062504]: Token expired (Failed to load magic link token)"
// Check for a specific error code
if (e.ErrorCode == "E061103")
{
// too many wrong OTP attempts
}
}As an example, the SDK automatically retries server errors (HTTP 503, 520, 521, 522, 524, 530) before raising. If a non-success response can't be parsed as a Descope error, the exception falls back to a synthetic code of HTTP<status> - for example HTTP401 for an unauthorized response:
catch (DescopeException e)
{
if (e.ErrorCode == "HTTP401")
{
// unauthorized (server returned 401 with no parseable error body)
}
}