Error Handling
The OnboardingHub API uses standard HTTP status codes and returns errors in a consistent JSON format.
Error format
All errors follow this structure:
{
"error": {
"type": "error_type",
"message": "Human-readable description of the error."
}
}
Validation errors include an additional errors object with field-level details:
{
"error": {
"type": "validation_error",
"message": "Email has already been taken, First name can't be blank",
"errors": {
"email": ["has already been taken"],
"first_name": ["can't be blank"]
}
}
}
Error types
| Type | HTTP Status | Description |
|---|---|---|
authentication_required |
401 | Missing or invalid Authorization header |
invalid_token |
401 | The API token is invalid or has been revoked |
token_expired |
401 | The API token or OAuth access token has expired |
forbidden |
403 | API access is not enabled for this account (plan upgrade required) |
insufficient_scope |
403 | The token does not have the required scope for this action |
not_found |
404 | The requested resource does not exist (or is not in your workspace) |
validation_error |
422 | The request body failed validation |
invalid_request |
400 | Missing required parameters |
rate_limited |
429 | Too many requests -- see Rate Limiting |
HTTP status codes
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Resource created |
204 |
Resource deleted (no content) |
400 |
Bad request (missing parameters) |
401 |
Unauthorized (invalid or missing token) |
403 |
Forbidden (insufficient scope or plan) |
404 |
Not found |
422 |
Unprocessable entity (validation failed) |
429 |
Too many requests (rate limited) |
Handling errors in code
Ruby
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
case response.code.to_i
when 200..299
data = JSON.parse(response.body)
when 401
raise "Authentication failed -- check your API key"
when 403
error = JSON.parse(response.body)
raise "Forbidden: #{error.dig('error', 'message')}"
when 404
raise "Resource not found"
when 422
error = JSON.parse(response.body)
field_errors = error.dig("error", "errors")
raise "Validation failed: #{field_errors}"
when 429
retry_after = response["Retry-After"].to_i
sleep(retry_after)
retry
end
Python
import requests
import time
response = requests.get(
"https://onboarding-hub.com/api/v1/contacts",
headers={"Authorization": "Bearer YOUR_ACCESS_TOKEN"},
)
if response.ok:
data = response.json()
elif response.status_code == 401:
raise Exception("Authentication failed -- check your API key")
elif response.status_code == 403:
error = response.json()
raise Exception(f"Forbidden: {error['error']['message']}")
elif response.status_code == 422:
error = response.json()
field_errors = error["error"].get("errors", {})
raise Exception(f"Validation failed: {field_errors}")
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
# Retry the request
JavaScript
const response = await fetch("https://onboarding-hub.com/api/v1/contacts", {
headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" },
});
if (response.ok) {
const data = await response.json();
} else if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
// Retry the request
} else {
const error = await response.json();
throw new Error(`API error: ${error.error.message}`);
}
Next steps
- Rate limiting -- handling 429 responses
- Endpoints reference -- available API endpoints