Reference

Error Reference

SDK error codes and how to handle them.

NexaError

All SDK errors extend NexaError:

try {
  await nexa.files.submit({ ... });
} catch (err) {
  if (err instanceof NexaError) {
    console.error(err.code, err.message, err.status);
  }
}
PropertyTypeDescription
codestringMachine-readable error code (see below)
messagestringHuman-readable description
statusnumberHTTP status code from the Nexa API

Error codes

Authentication errors

CodeStatusDescription
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403API key doesn't have permission for this operation
WEBHOOK_SIGNATURE_INVALID400HMAC signature doesn't match — possible replay or tamper

File processing errors

CodeStatusDescription
FILE_NOT_FOUND404No file with this fileId for the tenant
FILE_PROCESSING_FAILED422OCR/AI pipeline failed — check error field in webhook
FILE_QUOTA_EXCEEDED429Tenant page quota exhausted
INVALID_FILE_TYPE400Only PDF files are supported

Payment errors

CodeStatusDescription
PAYMENT_NOT_FOUND404No payment with this reference
PAYMENT_VERIFICATION_FAILED422Paystack could not verify this reference
PAYMENT_CONFIG_MISSING500Tenant payment config not set up — contact Nexa support

Email provisioning errors

CodeStatusDescription
EMAIL_CONFIG_MISSING400email.tier or email.domain not set in createNexa() config
EMAIL_DOMAIN_NOT_VERIFIED403Domain not yet verified — complete DNS setup in dashboard before provisioning
EMAIL_ACCOUNT_EXISTS409An account with this email address already exists
EMAIL_PROVIDER_ERROR502Stalwart or Google Workspace returned an error — check provider status
EMAIL_JOB_NOT_FOUND404No bulk provisioning job with this jobId

General errors

CodeStatusDescription
VALIDATION_ERROR400Request body failed validation — check message for details
RATE_LIMITED429Sliding window limit exceeded — read Retry-After and back off
UPSTREAM_ERROR502Nexa's upstream service returned an error
INTERNAL_ERROR500Unexpected server error — contact Nexa support

Rate limit errors

RATE_LIMITED (HTTP 429) responses always include a Retry-After header containing the number of seconds to wait before retrying.

Per-endpoint limits:

EndpointLimit
POST /api/file-processing/extract20 req / min per tenant
POST /api/payments/initialize10 req / min per tenant
POST /api/student-emails/bulk5 req / min per tenant
POST /api/rpc/[...orpc]120 req / min per user
All other routes60 req / min per tenant

The SDK automatically retries once on 429 using the Retry-After value. If the retry also fails, it throws:

try {
  await nexa.files.submit({ fileUrl, fileId, userId });
} catch (err) {
  if (err instanceof NexaError && err.code === "RATE_LIMITED") {
    const retryAfter = err.headers?.["retry-after"];
    console.warn(`Rate limited. Retry after ${retryAfter}s`);
  }
}

For manual retry without the SDK:

async function withRateLimitRetry(fn: () => Promise<Response>): Promise<Response> {
  const res = await fn();
  if (res.status === 429) {
    const wait = parseInt(res.headers.get("retry-after") ?? "5", 10);
    await new Promise((r) => setTimeout(r, wait * 1_000));
    return fn(); // one retry
  }
  return res;
}

Webhook errors

Webhook handlers should throw on failure so the SDK returns a non-2xx status, causing Nexa to retry:

onFileComplete: async ({ fileId, userId }) => {
  try {
    await db.save({ fileId, userId });
  } catch (err) {
    // throwing causes SDK to return 500 → Nexa retries
    throw err;
  }
},

Nexa retries failed webhooks up to 5 times with exponential backoff.

useFileProgress errors

const { error } = useFileProgress(fileId);
 
if (error) {
  // error is a plain string message from the SSE stream
  return <Alert>{error}</Alert>;
}

On this page