Error Codes
Complete reference of all API error responses and how to handle them.
HTTP Status Codes
| Code | Meaning | What to Do |
|---|---|---|
| 200 | Success | Request completed. Parse the response body. |
| 201 | Created | Resource created successfully. Response contains the new record. |
| 400 | Bad Request | Invalid input. Check the error message for which field failed validation. |
| 401 | Unauthorized | Token missing, invalid, or expired. User needs to reload the addon. |
| 403 | Forbidden | Token valid but scope not granted. Show "permission needed" message. |
| 404 | Not Found | Record doesn't exist or belongs to another org. |
| 429 | Rate Limited | Too many requests. Wait for Retry-After seconds, then retry. |
| 500 | Server Error | Something broke on our end. Retry once, then report if persistent. |
Error Response Format
Error Response
// All errors follow this format:
{
"error": "Human-readable error message"
}
// 403 responses include scope info:
{
"error": "Insufficient permissions. Required scope: customers.write",
"requiredScope": "customers.write",
"grantedScopes": ["customers.read", "invoices.read"]
}
// 429 responses include retry info:
{
"error": "Rate limit exceeded",
"limit": 1000,
"window": "1 hour",
"retryAfterSeconds": 1847
}Handling Errors in Code
error-handling.tsx
import { customers, formatError, ui } from '@syncbooks/addon-sdk'
// Basic error handling
try {
await customers.create({ name: '' })
} catch (error: any) {
console.log(error.status) // 400
console.log(error.message) // "name is required"
console.log(error.response) // Full error body
ui.notify(formatError(error), 'error')
}
// Retry with exponential backoff
import { retry } from '@syncbooks/addon-sdk'
const result = await retry(
() => customers.list({ limit: 100 }),
{ maxAttempts: 3, delayMs: 1000 }
)
// Global error handler
useEffect(() => {
const handler = (event: PromiseRejectionEvent) => {
const error = event.reason
if (error?.status === 401) {
setSessionExpired(true)
} else if (error?.status === 429) {
ui.notify('Please wait — rate limit reached', 'warning')
}
}
window.addEventListener('unhandledrejection', handler)
return () => window.removeEventListener('unhandledrejection', handler)
}, [])Common Issues & Solutions
"Loading..." forever
- Check that
SyncBooksProviderwraps your app - Check browser console for JavaScript errors
- Verify the addon is loaded inside SyncBooks (not standalone)
"No session token" error
- The
SyncBooksProviderhasn't received context yet - Wait for
ready === truebefore making API calls - If developing locally, use
configureClient()with a test token
403 on API calls that should work
- The organization didn't grant that scope during installation
- Check
scopesarray fromuseSyncBooks() - Ask the org admin to reinstall with the required scope
CORS errors
- The addon API allows all origins by design (auth is via token)
- If you see CORS errors, your backend URL might be wrong
- Check that you're calling
/api/addon/v1/not/api/v1/
Webhook not receiving events
- Verify your webhook URL is publicly accessible (not localhost)
- Check that your subscription is active:
events.subscriptions() - Subscriptions auto-disable after 3 failed deliveries — re-subscribe
- Verify your server responds with 200 within 10 seconds