Overview
RoZod provides flexible error handling with two approaches:
Union types (default) - Responses are typed as SuccessType | AnyError
Exceptions - Enable throwOnError: true to throw errors
Both approaches parse and structure Roblox error responses automatically.
Default error handling (union types)
By default, fetchApi returns either the success response or an AnyError object:
import { fetchApi , isAnyErrorResponse } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
const response = await fetchApi ( getGamesIcons , {
universeIds: [ 1534453623 ]
});
if ( isAnyErrorResponse ( response )) {
console . error ( 'Error:' , response . message );
console . error ( 'User-facing message:' , response . userFacingMessage );
return ;
}
// TypeScript knows response is the success type here
console . log ( response . data );
AnyError structure
The AnyError type contains parsed error information:
type AnyError = {
message : string ; // Error message
userFacingMessage ?: string ; // Optional user-friendly message
code ?: number ; // Optional error code
field ?: string ; // Optional field that caused the error
retryable ?: boolean ; // Whether the error is retryable
};
Use isAnyErrorResponse() as a type guard to narrow the response type and access error properties safely.
Exception-based error handling
Enable throwOnError: true to use try/catch instead:
import { fetchApi } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
try {
const response = await fetchApi (
getGamesIcons ,
{ universeIds: [ 1534453623 ] },
{ throwOnError: true }
);
// response is typed as the success type only
console . log ( response . data );
} catch ( error ) {
console . error ( 'Failed to fetch game icons:' , ( error as Error ). message );
}
With throwOnError: true:
Successful responses return the expected type directly
Failed requests throw an Error with the message from AnyError.userFacingMessage or AnyError.message
Error types by API
RoZod automatically parses different Roblox error formats:
Classic API errors (BEDEV1)
Classic Roblox APIs (e.g., users.roblox.com) use the BEDEV1 format:
{
"errors" : [
{
"code" : 3 ,
"message" : "The user is invalid." ,
"userFacingMessage" : "Something went wrong"
}
]
}
Parsed into AnyError:
{
code : 3 ,
message : "The user is invalid." ,
userFacingMessage : "Something went wrong"
}
OpenCloud errors (BEDEV2)
OpenCloud APIs (e.g., apis.roblox.com) use the BEDEV2 format:
{
"error" : "INVALID_ARGUMENT" ,
"message" : "universe_id is required" ,
"details" : [
{
"@type" : "type.googleapis.com/google.rpc.BadRequest" ,
"fieldViolations" : [
{
"field" : "universe_id" ,
"description" : "Required field missing"
}
]
}
]
}
Parsed into AnyError:
{
code : "INVALID_ARGUMENT" ,
message : "universe_id is required" ,
field : "universe_id" ,
userFacingMessage : "Required field missing"
}
Generic HTTP errors
For non-Roblox domains, RoZod provides best-effort error parsing:
// JSON error response
{
message : "Error message from JSON response"
}
// Text error response
{
message : "Error text from response body (truncated to 1000 chars)"
}
// No response body
{
message : "HTTP 500" // Falls back to status text
}
Handling specific errors
Check error codes to handle specific scenarios:
import { fetchApi , isAnyErrorResponse } from 'rozod' ;
import { getUsersUserdetails } from 'rozod/lib/endpoints/usersv1' ;
const response = await fetchApi ( getUsersUserdetails , {
userIds: [ 123456 ]
});
if ( isAnyErrorResponse ( response )) {
switch ( response . code ) {
case 3 :
console . error ( 'Invalid user' );
break ;
case 401 :
console . error ( 'Authentication required' );
break ;
default :
console . error ( 'Unknown error:' , response . message );
}
return ;
}
console . log ( response . data );
Network errors
Network failures (DNS errors, timeouts, connection issues) throw standard JavaScript errors:
import { fetchApi } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
try {
const response = await fetchApi ( getGamesIcons , {
universeIds: [ 1534453623 ]
});
if ( isAnyErrorResponse ( response )) {
// Handle API errors
console . error ( 'API error:' , response . message );
} else {
// Success
console . log ( response . data );
}
} catch ( error ) {
// Handle network errors (DNS failure, timeout, etc.)
console . error ( 'Network error:' , ( error as Error ). message );
}
Network errors always throw regardless of the throwOnError setting. Use try/catch to handle these.
Retry logic
Add retry logic for transient failures:
import { fetchApi } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
const response = await fetchApi (
getGamesIcons ,
{ universeIds: [ 1534453623 ] },
{
retries: 3 ,
retryDelay: 1000 , // 1 second between retries
}
);
Retry options:
retries - Number of retry attempts (default: 0)
retryDelay - Milliseconds to wait between retries (default: 0)
Retries only apply to network-level failures, not API errors. A 400 Bad Request won’t be retried automatically.
Paginated error handling
When fetching multiple pages, the first error stops iteration:
import { fetchApiPages , isAnyErrorResponse } from 'rozod' ;
import { getGroupsGroupidWallPosts } from 'rozod/lib/endpoints/groupsv2' ;
const pages = await fetchApiPages (
getGroupsGroupidWallPosts ,
{ groupId: 11479637 }
);
if ( isAnyErrorResponse ( pages )) {
console . error ( 'Failed to fetch pages:' , pages . message );
return ;
}
// pages is an array of successful responses
console . log ( `Fetched ${ pages . length } pages` );
With the generator, check each page individually:
import { fetchApiPagesGenerator , isAnyErrorResponse } from 'rozod' ;
import { getGroupsGroupidWallPosts } from 'rozod/lib/endpoints/groupsv2' ;
const generator = fetchApiPagesGenerator (
getGroupsGroupidWallPosts ,
{ groupId: 11479637 }
);
for await ( const page of generator ) {
if ( isAnyErrorResponse ( page )) {
console . error ( 'Error on page:' , page . message );
break ; // Stop iteration
}
console . log ( `Processing ${ page . data . length } posts` );
}
Batch request errors
fetchApiSplit returns an error if any batch fails:
import { fetchApiSplit , isAnyErrorResponse } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
const results = await fetchApiSplit (
getGamesIcons ,
{ universeIds: Array . from ({ length: 500 }, ( _ , i ) => i + 1 ) },
{ universeIds: 100 }, // Split into batches of 100
);
if ( isAnyErrorResponse ( results )) {
console . error ( 'Batch request failed:' , results . message );
return ;
}
// results is an array of successful batch responses
console . log ( `Processed ${ results . length } batches` );
Raw response access
For advanced error handling, access the raw Response object:
import { fetchApi } from 'rozod' ;
import { getGamesIcons } from 'rozod/lib/endpoints/gamesv1' ;
const response = await fetchApi (
getGamesIcons ,
{ universeIds: [ 1534453623 ] },
{ returnRaw: true }
);
if ( ! response . ok ) {
console . error ( 'Status:' , response . status );
console . error ( 'Status text:' , response . statusText );
console . error ( 'Headers:' , Object . fromEntries ( response . headers ));
const body = await response . text ();
console . error ( 'Body:' , body );
return ;
}
const data = await response . json ();
console . log ( data );
With returnRaw: true, you get the full Response object and can inspect status codes, headers, and raw body content.
Best practices
Choose the right approach
Use union types (default) when errors are expected and need different handling logic
Use throwOnError when errors are exceptional and you want simpler happy-path code
Always handle errors
// Good: Checks for errors
const response = await fetchApi ( endpoint , params );
if ( isAnyErrorResponse ( response )) {
return handleError ( response );
}
processSuccess ( response );
// Bad: Assumes success
const response = await fetchApi ( endpoint , params );
processSuccess ( response ); // TypeScript error if not checked!
Provide user feedback
Use userFacingMessage when available for end-user display:
if ( isAnyErrorResponse ( response )) {
// Show user-friendly message if available
const displayMessage = response . userFacingMessage || response . message ;
showErrorToUser ( displayMessage );
// Log technical details for debugging
console . error ( 'Error details:' , response );
}
Handle network errors separately
try {
const response = await fetchApi ( endpoint , params );
if ( isAnyErrorResponse ( response )) {
// API error - show user message
showError ( response . userFacingMessage || response . message );
} else {
// Success
processData ( response );
}
} catch ( error ) {
// Network error - generic message
showError ( 'Unable to connect. Please check your internet connection.' );
console . error ( 'Network error:' , error );
}
Next steps
Type safety Learn about TypeScript type inference and validation.
Authentication Understand authentication and security features.