Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt

Use this file to discover all available pages before exploring further.

When an SDK method fails, it throws IMessageError or one of its subclasses. First branch by error class with instanceof, then use error.code for the exact reason. Do not parse error.message for program logic. message is useful for logs and user-facing text. Use error.code, error.retryable, and error.context for decisions.

What You Can Do

NeedUse
Distinguish auth, not-found, rate-limit, validation, and connection errorserror instanceof ...
Check the exact reasonerror.code
Decide whether the same request is worth retryingerror.retryable
Read structured server contexterror.context
Prevent duplicate writes from retried jobsclientMessageId

Handle Errors

Check the most specific subclasses first, and handle IMessageError last. If the error is not from the SDK, rethrow it.
import {
  AuthenticationError,
  ConnectionError,
  IMessageError,
  NotFoundError,
  RateLimitError,
  ValidationError,
} from "@photon-ai/advanced-imessage";

try {
  await im.messages.sendText(chat.guid, "Hello");
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(error.retryable, error.context);
  } else if (error instanceof NotFoundError) {
    console.log(error.code);
  } else if (error instanceof AuthenticationError) {
    console.log("refresh credentials");
  } else if (error instanceof ValidationError) {
    console.log(error.message, error.context);
  } else if (error instanceof ConnectionError) {
    console.log("network or timeout failure");
  } else if (error instanceof IMessageError) {
    console.log(error.code, error.grpcCode, error.context);
  } else {
    throw error;
  }
}
Error classUsually meansCommon handling
AuthenticationErrorToken rejected, expired, or unauthorizedRefresh credentials or stop sending
NotFoundErrorChat, message, attachment, poll, address, or icon does not existRefresh local state and stop using the stale GUID
RateLimitErrorServer quota or rate limit rejected the requestRetry later if retryable and your business queue allows it
ValidationErrorInvalid input or failed preconditionFix the input; do not retry unchanged
ConnectionErrorNetwork failure, timeout, or server unavailableRetry according to your retry policy
IMessageErrorOther SDK errorLog code, grpcCode, and context; use your fallback path

Error Object

All SDK errors include these fields:
{
  "name":      "NotFoundError",   // Error class name
  "message":   "message not found",
  "code":      "messageNotFound", // Stable code for program logic
  "retryable": false,             // Whether the same request may succeed later
  "grpcCode":  5,                 // Numeric gRPC status, mainly for debugging
  "context": {                    // Structured server context; may be empty
    "message": "missing-message-guid"
  }
}
FieldHow to use it
codeProgram branches and exact error handling
retryableDecide whether the same request can be retried
grpcCodeDebug low-level transport state
contextSee which field, GUID, or resource caused the failure
messageLogs or user-facing copy
causeOriginal lower-level error when the SDK wraps one; may be absent
Do not parse message. Server wording can change. Use error.code for stable decisions.

Common Error Codes

ErrorCode is both a runtime object and a TypeScript type. In most code, check the specific error class first, then compare error.code:
import { ErrorCode, NotFoundError } from "@photon-ai/advanced-imessage";

try {
  await im.messages.get(chat.guid, "missing-message-guid");
} catch (error) {
  if (error instanceof NotFoundError && error.code === ErrorCode.messageNotFound) {
    console.log("message no longer exists");
  }
}
Common codes by category:
CategoryError codes
Authenticationunauthenticated, tokenExpired, tokenBlocked, unauthorized
Rate limitsdailyLimitExceeded, recipientLimitExceeded, uploadRateExceeded, contentDuplicateExceeded, recipientCoolingDown, recipientLocked, sendReceiveRatioExceeded
Duplicate writesduplicateMessage
Not foundchatNotFound, messageNotFound, attachmentNotFound, addressNotFound, sharedFriendLocationNotFound, groupIconNotFound, pollNotFound
ValidationinvalidArgument, preconditionFailed, operationNotSupported, attachmentNotReady, privateApiUnavailable
InfrastructureserviceUnavailable, timeout, internalError, databaseError, networkError
The server may return new error codes before your SDK version exports matching constants. Compare with ErrorCode constants when possible, and keep a fallback path for unknown error.code values.

gRPC Status Mapping

Most application code should not branch on grpcCode. Prefer error classes and error.code. Use this table when debugging transport-level behavior.
gRPC statusSDK error class
UNAUTHENTICATED, PERMISSION_DENIEDAuthenticationError
NOT_FOUNDNotFoundError
RESOURCE_EXHAUSTEDRateLimitError
INVALID_ARGUMENT, FAILED_PRECONDITIONValidationError
UNAVAILABLE, DEADLINE_EXCEEDEDConnectionError
Everything elseIMessageError

Retries

Set retry when creating the client. The SDK retries retryable unary requests automatically. Invalid input, missing resources, and permission failures do not become valid by retrying the same request.
const im = createClient({
  address: "imessage.example.com:443",
  token: process.env.IMESSAGE_TOKEN!,
  retry: {
    maxAttempts: 3,
    initialDelay: 200,
    maxDelay: 5000,
  },
});
RetryOptions:
OptionMeaning
retry: trueUse the SDK default retry policy
maxAttemptsMaximum attempts, including the first request
initialDelayDelay before the first retry, in milliseconds
maxDelayMaximum delay between retries, in milliseconds
CaseAutomatically retried
Unary request with error.retryable === trueYes
ValidationError or invalid inputNo
Live streams, download streams, location streamsNo
Streaming requests are not retried automatically. When message, chat, group, or poll live streams disconnect, follow the concurrent recovery flow in events: consume live streams and im.events.catchUp(...) together. Location streams are different; location updates cannot be caught up.

Idempotency

Most calls do not need clientMessageId. Use it only when your queue or worker may rerun the same logical write after a crash or timeout. Automatic retry handles the same SDK call. clientMessageId handles your business job starting the same write again. Use the same clientMessageId every time you retry the same logical write:
await im.messages.sendText(chat.guid, "Hello", {
  clientMessageId: `job-${job.id}`,
});
CaseResult
Same clientMessageId + same writeServer treats it as a duplicate and returns the original result
New clientMessageIdServer treats it as a new independent write
Do not reuse one clientMessageId for different business operations. It represents one logical write, not a user ID, chat ID, or long-lived session ID.

Next Steps

  1. Events — recover after stream disconnects
  2. Messages — understand write methods, idempotency keys, and message errors
  3. Attachments — handle attachmentNotReady