Error Handling
Understanding and handling errors is crucial for building robust integrations with the Billbora API. This guide covers all error types, response formats, and best practices.
All Billbora API errors follow a consistent JSON structure:
{
"error" : {
"code" : "validation_error" ,
"message" : "Invalid input data" ,
"details" : {
"email" : [ "Must be a valid email address" ],
"amount" : [ "Must be a positive number" ]
}
},
"meta" : {
"request_id" : "req_abc123xyz" ,
"timestamp" : "2025-01-15T10:30:00Z"
}
}
HTTP Status Codes
200 OK : Request successful
201 Created : Resource created successfully
204 No Content : Resource deleted successfully
400 Bad Request : Invalid request parameters
401 Unauthorized : Authentication required
403 Forbidden : Insufficient permissions
404 Not Found : Resource not found
409 Conflict : Resource conflict (duplicate data)
422 Unprocessable Entity : Validation failed
429 Too Many Requests : Rate limit exceeded
500 Internal Server Error : Unexpected server error
502 Bad Gateway : Upstream service error
503 Service Unavailable : Service temporarily unavailable
504 Gateway Timeout : Request timeout
Common Error Codes
Authentication Errors
missing_token
invalid_token
insufficient_permissions
{
"error" : {
"code" : "missing_token" ,
"message" : "Authentication token is required"
}
}
Validation Errors
validation_error
missing_required_field
{
"error" : {
"code" : "validation_error" ,
"message" : "Invalid input data" ,
"details" : {
"email" : [ "Must be a valid email address" ],
"line_items" : [ "At least one line item is required" ]
}
}
}
Resource Errors
{
"error" : {
"code" : "not_found" ,
"message" : "The requested resource was not found" ,
"details" : {
"resource" : "invoice" ,
"id" : "inv_1234567890"
}
}
}
Rate Limiting
{
"error" : {
"code" : "rate_limit_exceeded" ,
"message" : "Rate limit exceeded. Please try again later." ,
"details" : {
"limit" : 1000 ,
"reset_time" : "2025-01-15T11:00:00Z" ,
"retry_after" : 3600
}
}
}
Error Handling Best Practices
1. Always Check Status Codes
const response = await fetch ( 'https://api.billbora.com/api/v1/invoices/' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer YOUR_API_KEY' ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ( invoiceData )
})
if ( ! response . ok ) {
const error = await response . json ()
throw new Error ( `API Error: ${ error . error . message } ` )
}
const invoice = await response . json ()
2. Handle Specific Error Types
enum BillboraErrorCode {
VALIDATION_ERROR = 'validation_error' ,
NOT_FOUND = 'not_found' ,
RATE_LIMITED = 'rate_limit_exceeded' ,
INSUFFICIENT_PERMISSIONS = 'insufficient_permissions'
}
class BillboraError extends Error {
constructor (
public code : BillboraErrorCode ,
public message : string ,
public details ?: Record < string , string []>,
public statusCode ?: number
) {
super ( message )
this . name = 'BillboraError'
}
}
const handleApiError = ( error : any ) : never => {
switch ( error . error . code ) {
case BillboraErrorCode . VALIDATION_ERROR :
// Show field-specific validation errors
const fieldErrors = error . error . details
throw new BillboraError (
BillboraErrorCode . VALIDATION_ERROR ,
'Please fix the validation errors' ,
fieldErrors ,
400
)
case BillboraErrorCode . NOT_FOUND :
throw new BillboraError (
BillboraErrorCode . NOT_FOUND ,
'The requested resource was not found' ,
undefined ,
404
)
case BillboraErrorCode . RATE_LIMITED :
const retryAfter = error . error . details ?. retry_after || 3600
throw new BillboraError (
BillboraErrorCode . RATE_LIMITED ,
`Rate limit exceeded. Retry after ${ retryAfter } seconds` ,
undefined ,
429
)
default :
throw new BillboraError (
error . error . code ,
error . error . message ,
error . error . details ,
error . status
)
}
}
3. Implement Retry Logic
Exponential Backoff
Python Retry Decorator
async function apiCallWithRetry ( apiCall , maxRetries = 3 ) {
for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
try {
return await apiCall ()
} catch ( error ) {
if ( error . status === 429 ) {
// Rate limited - wait and retry
const retryAfter = error . details ?. retry_after || Math . pow ( 2 , attempt ) * 1000
await new Promise ( resolve => setTimeout ( resolve , retryAfter ))
continue
}
if ( error . status >= 500 && attempt < maxRetries ) {
// Server error - retry with exponential backoff
const delay = Math . pow ( 2 , attempt ) * 1000
await new Promise ( resolve => setTimeout ( resolve , delay ))
continue
}
// Don't retry client errors (4xx)
throw error
}
}
}
// Usage
const invoice = await apiCallWithRetry (() =>
createInvoice ( invoiceData )
)
Rate Limiting
Understanding Rate Limits
Billbora implements rate limiting to ensure fair usage:
General API : 1,000 requests per hour per user
Authentication : 10 requests per minute per IP
Bulk operations : 100 requests per hour per user
Every API response includes rate limit information:
HTTP / 1.1 200 OK
X-RateLimit-Limit : 1000
X-RateLimit-Remaining : 999
X-RateLimit-Reset : 1642694400
X-RateLimit-Reset-After : 3600
Handling Rate Limits
Check Headers
Monitor Usage
const response = await fetch ( 'https://api.billbora.com/api/v1/invoices/' )
const remaining = parseInt ( response . headers . get ( 'X-RateLimit-Remaining' ))
const resetTime = parseInt ( response . headers . get ( 'X-RateLimit-Reset' ))
if ( remaining < 10 ) {
console . warn ( 'Rate limit nearly exceeded' )
// Consider slowing down requests
}
if ( response . status === 429 ) {
const retryAfter = response . headers . get ( 'Retry-After' )
console . log ( `Rate limited. Retry after ${ retryAfter } seconds` )
}
Debugging API Errors
1. Use Request IDs
Every API response includes a request ID for debugging:
{
"meta" : {
"request_id" : "req_abc123xyz" ,
"timestamp" : "2025-01-15T10:30:00Z"
}
}
When contacting support, always include the request ID.
2. Enable Request Logging
Request Logging
Request Logging
const originalFetch = fetch
window . fetch = async ( ... args ) => {
const [ url , options ] = args
const requestId = `req_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). substr ( 2 , 9 ) } `
console . log ( `[ ${ requestId } ] Request:` , {
url ,
method: options ?. method || 'GET' ,
headers: options ?. headers ,
body: options ?. body
})
try {
const response = await originalFetch ( ... args )
const clonedResponse = response . clone ()
const responseData = await clonedResponse . json ()
console . log ( `[ ${ requestId } ] Response:` , {
status: response . status ,
headers: Object . fromEntries ( response . headers . entries ()),
data: responseData
})
return response
} catch ( error ) {
console . error ( `[ ${ requestId } ] Error:` , error )
throw error
}
}
3. Validation Error Details
For validation errors, the API provides detailed field-level error messages:
{
"error" : {
"code" : "validation_error" ,
"message" : "Invalid input data" ,
"details" : {
"line_items" : [
"At least one line item is required"
],
"line_items.0.unit_price" : [
"Must be a valid monetary amount"
],
"client" : [
"This field is required"
]
}
}
}
Use these details to show specific error messages to users.
Error Recovery Strategies
1. Graceful Degradation
async function createInvoiceWithFallback ( invoiceData ) {
try {
return await billboraAPI . invoices . create ( invoiceData )
} catch ( error ) {
if ( error . code === 'validation_error' ) {
// Show validation errors to user
showValidationErrors ( error . details )
return null
}
if ( error . code === 'rate_limit_exceeded' ) {
// Queue for later processing
queueForLater ( invoiceData )
showMessage ( 'Invoice queued for processing' )
return null
}
// For other errors, show generic message and log details
logError ( error )
showMessage ( 'Something went wrong. Please try again.' )
return null
}
}
2. Offline Support
class OfflineInvoiceQueue {
constructor () {
this . queue = JSON . parse ( localStorage . getItem ( 'invoice_queue' ) || '[]' )
}
add ( invoiceData ) {
this . queue . push ({
id: Date . now (),
data: invoiceData ,
timestamp: new Date (). toISOString ()
})
localStorage . setItem ( 'invoice_queue' , JSON . stringify ( this . queue ))
}
async processQueue () {
const items = [ ... this . queue ]
this . queue = []
localStorage . setItem ( 'invoice_queue' , JSON . stringify ( this . queue ))
for ( const item of items ) {
try {
await billboraAPI . invoices . create ( item . data )
console . log ( `Processed queued invoice ${ item . id } ` )
} catch ( error ) {
// Re-queue if still failing
this . add ( item . data )
console . error ( `Failed to process queued invoice ${ item . id } :` , error )
}
}
}
}
Getting Help
When you encounter errors that you can’t resolve:
Pro Tip : Always include the request ID when reporting issues. It helps our support team diagnose problems much faster.