AliasKit
Docs/Error Handling

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:

json
{
  "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:

json
{
  "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

400validation_error

Request body is invalid or missing required fields.

Cause: Malformed JSON, missing required fields, invalid field values.
Fix: Check the error.fields object for per-field messages.

Authentication

401auth_required

No API key was provided in the request.

Cause: The Authorization header is missing or empty.
Fix: Include your API key in the Authorization header as `Bearer ak_live_...`.
401auth_invalid

The API key does not exist or has been revoked.

Cause: The key was deleted from the dashboard, or the value is incorrect.
Fix: Generate a new key from the dashboard and update your configuration.
403auth_forbidden

The API key is valid but lacks the required scope for this endpoint.

Cause: The key was created with restricted scopes that do not include this operation.
Fix: Create a new key with the necessary scopes, or update the existing key's permissions in the dashboard.

Billing

402upgrade_required

This feature requires a higher plan than your current subscription.

Cause: You attempted to use a feature not available on your current plan.
Fix: Upgrade your plan at aliaskit.com/dashboard/billing.
402free_tier_identity_limit

You have reached the identity limit on the free tier.

Cause: The free plan allows a limited number of active identities. You have hit that cap.
Fix: Upgrade to a paid plan for higher limits.
402free_tier_phone_blocked

Phone numbers are not available on the free tier.

Cause: You attempted to create an identity with a phone number while on the free plan.
Fix: Upgrade to the Pro plan to provision phone numbers.
402free_tier_phone_number_limit

You have reached the phone number limit on the free tier.

Cause: The free plan restricts how many phone numbers you can provision.
Fix: Release unused phone numbers or upgrade your plan.
402free_tier_cards_blocked

Virtual cards are not available on the free tier.

Cause: You attempted to provision a virtual card while on the free plan.
Fix: Upgrade to a plan that includes virtual card support.
402free_tier_card_limit

You have reached the virtual card limit on the free tier.

Cause: The free plan restricts how many virtual cards you can create.
Fix: Delete unused cards or upgrade your plan for higher limits.
402free_tier_totp_blocked

TOTP secrets are not available on the free tier.

Cause: You attempted to register a TOTP secret while on the free plan.
Fix: Upgrade to a plan that includes TOTP support.
402free_tier_custom_domains_blocked

Custom email domains are not available on the free tier.

Cause: You attempted to configure a custom domain while on the free plan.
Fix: Upgrade to a plan that includes custom domain support.

See Plans and Limits for the feature matrix.

Not found

404identity_not_found

The requested identity does not exist or belongs to a different API key.

Cause: The identity ID is incorrect, or it was created with a different key.
Fix: Verify the identity ID and ensure you are using the same API key that created it.
404email_not_found

The requested email message does not exist.

Cause: The email ID is incorrect, the message was deleted, or it belongs to a different identity.
Fix: List the identity's emails first to confirm the message ID.
404sms_not_found

The requested SMS message does not exist.

Cause: The SMS ID is incorrect, the message was deleted, or it belongs to a different identity.
Fix: List the identity's SMS messages first to confirm the message ID.
404agent_not_found

The requested agent profile does not exist.

Cause: The agent ID is incorrect or the agent was deleted.
Fix: Verify the agent ID and check that the agent has not been removed.

Rate limiting

429rate_limit_exceeded

You have sent too many requests in a short period.

Cause: Your request rate exceeded the allowed limit for your plan.
Fix: Back off and retry with exponential delay. Check the Retry-After header for guidance.

Server errors (5xx)

500internal_error

An unexpected error occurred on the server.

Cause: A bug or unhandled edge case in the AliasKit API.
Fix: Retry the request. If the error persists, contact support with the request ID from the response headers.
503service_unavailable

The service is temporarily unavailable.

Cause: A planned maintenance window or temporary infrastructure issue.
Fix: Retry after a short delay. Check status.aliaskit.com for ongoing incidents.

SDK error classes

The SDK throws typed errors so you can catch specific failure modes. All error classes extend the base AliasKitError.

typescript
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.

typescript
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.

typescript
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
typescript
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
typescript
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
typescript
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")
typescript
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.

typescript
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:

typescript
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:

typescript
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_KEY environment variable is set and starts with ak_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 id property.

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:

typescript
// 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:

typescript
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();