Error Handling
Every API call can fail. AliasKit uses consistent error codes and response shapes so you can handle failures predictably in both direct API calls and SDK usage.
Error response format
All errors return a JSON body with a top-level error object:
{
"error": {
"code": "validation_error",
"message": "Request body is missing required field: email"
}
}The code field is a stable, machine-readable string you can match against in your error handling logic. The message field is a human-readable description that may change between versions.
Some errors include additional fields depending on the code:
{
"error": {
"code": "validation_error",
"message": "Request body is invalid.",
"fields": {
"email": "Must be a valid email address",
"metadata": "Must be a JSON object"
}
}
}Client errors (4xx)
Validation
validation_errorRequest body is invalid or missing required fields.
Authentication
auth_requiredNo API key was provided in the request.
auth_invalidThe API key does not exist or has been revoked.
auth_forbiddenThe API key is valid but lacks the required scope for this endpoint.
Billing
upgrade_requiredThis feature requires a higher plan than your current subscription.
free_tier_identity_limitYou have reached the identity limit on the free tier.
free_tier_phone_blockedPhone numbers are not available on the free tier.
free_tier_phone_number_limitYou have reached the phone number limit on the free tier.
free_tier_cards_blockedVirtual cards are not available on the free tier.
free_tier_card_limitYou have reached the virtual card limit on the free tier.
free_tier_totp_blockedTOTP secrets are not available on the free tier.
free_tier_custom_domains_blockedCustom email domains are not available on the free tier.
See Plans and Limits for the feature matrix.
Not found
identity_not_foundThe requested identity does not exist or belongs to a different API key.
email_not_foundThe requested email message does not exist.
sms_not_foundThe requested SMS message does not exist.
agent_not_foundThe requested agent profile does not exist.
Rate limiting
rate_limit_exceededYou have sent too many requests in a short period.
Server errors (5xx)
internal_errorAn unexpected error occurred on the server.
service_unavailableThe service is temporarily unavailable.
SDK error classes
The SDK throws typed errors so you can catch specific failure modes. All error classes extend the base AliasKitError.
import {
AliasKitError,
AuthError,
ApiError,
EmailTimeoutError,
SmsTimeoutError,
BillingError,
BillingErrorCode,
} from "aliaskit";AliasKitError
Base class for all SDK errors. Every error has a code property with a stable string you can match on.
try {
await ak.identities.create();
} catch (err) {
if (err instanceof AliasKitError) {
console.log(err.code); // e.g. "auth_required"
console.log(err.message); // human-readable description
}
}AuthError
Extends AliasKitError. Thrown when authentication fails because the key is missing, invalid, or lacks required scopes.
try {
await ak.identities.list();
} catch (err) {
if (err instanceof AuthError) {
console.log(err.code); // "auth_required" | "auth_invalid" | "auth_forbidden"
}
}ApiError
Extends AliasKitError. Thrown for any HTTP error response from the API. Includes the full response context for debugging.
- •
status- HTTP status code (e.g. 400, 404, 500) - •
endpoint- the API endpoint that returned the error - •
responseBody- the raw response body as a string
try {
await ak.identities.get("nonexistent-id");
} catch (err) {
if (err instanceof ApiError) {
console.log(err.status); // 404
console.log(err.endpoint); // "/v1/identities/nonexistent-id"
console.log(err.responseBody); // raw JSON string
}
}EmailTimeoutError
Thrown when ak.emails.waitForCode or ak.emails.waitFor does not receive a matching email within the specified timeout.
- •
timeoutMs- the timeout value in milliseconds
try {
const code = await ak.emails.waitForCode(identity.id, { timeout: 15_000 });
} catch (err) {
if (err instanceof EmailTimeoutError) {
console.log(err.timeoutMs); // 15000
}
}SmsTimeoutError
Thrown when ak.sms.waitForCode or ak.sms.waitFor does not receive a matching SMS within the specified timeout.
- •
timeoutMs- the timeout value in milliseconds
try {
const code = await ak.sms.waitForCode(identity.id, { timeout: 15_000 });
} catch (err) {
if (err instanceof SmsTimeoutError) {
console.log(err.timeoutMs); // 15000
}
}BillingError
Thrown for any 402 response. Includes a requiredPlan property derived from the error code so you can display actionable upgrade prompts.
- •
requiredPlan- the plan name required to access the feature (e.g. "pro", "business")
try {
await ak.identities.create({ phone: true });
} catch (err) {
if (err instanceof BillingError) {
console.log(err.code); // "free_tier_phone_blocked"
console.log(err.requiredPlan); // "pro"
}
}BillingErrorCode
A const object containing all billing error code strings. Useful for exhaustive matching without hardcoding string literals.
import { BillingErrorCode } from "aliaskit";
switch (err.code) {
case BillingErrorCode.IDENTITY_LIMIT:
// handle identity limit
break;
case BillingErrorCode.PHONE_BLOCKED:
// handle phone blocked
break;
case BillingErrorCode.CARDS_BLOCKED:
// handle cards blocked
break;
}Handling errors
Exponential backoff
For rate limit and transient server errors, use exponential backoff with jitter:
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err instanceof ApiError) {
const retryable = err.status === 429 || err.status >= 500;
if (retryable && attempt < maxRetries) {
const delay = Math.min(1000 * 2 ** attempt, 10_000);
const jitter = Math.random() * delay * 0.1;
await new Promise((r) => setTimeout(r, delay + jitter));
continue;
}
}
throw err;
}
}
throw new Error("Unreachable");
}
// Usage
const identity = await withRetry(() => ak.identities.create());Error recovery by code
Handle specific error codes to provide targeted recovery logic:
try {
const identity = await ak.identities.create({ phone: true });
const code = await ak.emails.waitForCode(identity.id, { timeout: 30_000 });
} catch (err) {
if (err instanceof BillingError) {
console.log(`Upgrade to ${err.requiredPlan} to use this feature.`);
} else if (err instanceof EmailTimeoutError) {
console.log("No email received. Check that the signup flow sends to the right address.");
} else if (err instanceof AuthError) {
console.log("API key is invalid. Regenerate it from the dashboard.");
} else if (err instanceof ApiError && err.status === 404) {
console.log("Identity not found. It may have been deleted.");
} else {
throw err;
}
}Common issues and troubleshooting
Unauthorized on every request
If every request returns auth_required or auth_invalid, check the following:
- •Verify the
ALIASKIT_API_KEYenvironment variable is set and starts withak_live_. - •Confirm the key has not been revoked in the dashboard.
- •Make sure the Authorization header format is
Bearer ak_live_...with a space after "Bearer".
Identity not found when it should exist
This usually happens when:
- •The identity was created with a different API key. Each key has its own isolated set of identities.
- •The identity ID is being confused with another field like the email address. Always use the
idproperty.
Rate limit errors from manual polling
If you are calling ak.emails.list or ak.sms.list in a loop, you will hit rate limits quickly. Use the built-in helpers instead, which use realtime WebSocket subscriptions under the hood:
// Bad: manual polling (burns rate limit)
while (true) {
const emails = await ak.emails.list(identity.id);
if (emails.length > 0) break;
await new Promise((r) => setTimeout(r, 1000));
}
// Good: uses realtime subscription internally
const email = await ak.emails.waitForCode(identity.id, {
timeout: 30_000,
});Realtime events for production
For production workflows that run frequently, polling-based approaches add latency and consume rate limit budget. Use realtime subscriptions instead:
const subscription = ak.realtime.subscribe(identity.id, {
events: ["email.received", "sms.received"],
onEvent: (event) => {
console.log(`Received ${event.type}:`, event.data);
},
});
// Trigger the signup flow...
// Clean up when done
subscription.unsubscribe();