or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-client.mdbase-stream-classes.mdcrm-streams.mdcustom-objects.mdengagement-streams.mderror-handling.mdindex.mdmarketing-sales-streams.mdproperty-history-streams.mdsource-connector.mdweb-analytics.md

error-handling.mddocs/

0

# Error Handling

1

2

Comprehensive error handling classes for HubSpot API-specific errors including authentication, rate limiting, timeouts, and permission issues. The connector provides detailed error information and appropriate retry strategies.

3

4

## Capabilities

5

6

### Base Error Class

7

8

Foundation error class for all HubSpot-specific exceptions.

9

10

```python { .api }

11

class HubspotError(AirbyteTracedException):

12

"""

13

Base error class for HubSpot connector exceptions.

14

15

Subclasses HTTPError to maintain compatibility with existing

16

error handling code while providing enhanced error context.

17

"""

18

19

def __init__(

20

self,

21

internal_message: str = None,

22

message: str = None,

23

failure_type: FailureType = FailureType.system_error,

24

exception: BaseException = None,

25

response: requests.Response = None

26

):

27

"""

28

Initialize HubSpot error with detailed context.

29

30

Parameters:

31

- internal_message: Internal error message for debugging

32

- message: User-facing error message

33

- failure_type: Classification of failure type

34

- exception: Original exception that caused this error

35

- response: HTTP response object if applicable

36

"""

37

```

38

39

### Authentication Errors

40

41

Errors related to authentication and authorization failures.

42

43

```python { .api }

44

class HubspotInvalidAuth(HubspotError):

45

"""

46

401 Unauthorized error.

47

48

Raised when:

49

- Invalid API credentials provided

50

- OAuth token has expired or been revoked

51

- Private app token is invalid or malformed

52

- Cloudflare DNS error (530) indicating token format issues

53

54

This error indicates the user needs to check and refresh

55

their authentication credentials.

56

"""

57

58

class HubspotAccessDenied(HubspotError):

59

"""

60

403 Forbidden error.

61

62

Raised when:

63

- User lacks permissions for requested resource

64

- OAuth scopes are insufficient for the operation

65

- Private app permissions don't include required access

66

67

This error indicates the user needs additional permissions

68

or OAuth scopes to access the requested resource.

69

"""

70

```

71

72

### Rate Limiting & Throttling

73

74

Errors related to API rate limits and request throttling.

75

76

```python { .api }

77

class HubspotRateLimited(HTTPError):

78

"""

79

429 Too Many Requests error.

80

81

Raised when HubSpot API rate limits are exceeded.

82

83

The connector automatically handles retry logic based on

84

the 'Retry-After' header provided by HubSpot.

85

86

Rate limits vary by:

87

- API endpoint type

88

- HubSpot subscription level

89

- Authentication method (OAuth vs Private App)

90

"""

91

```

92

93

### Server & Timeout Errors

94

95

Errors related to server issues and request timeouts.

96

97

```python { .api }

98

class HubspotTimeout(HTTPError):

99

"""

100

502/503/504 Server timeout errors.

101

102

HubSpot has processing limits to prevent performance degradation.

103

These responses indicate those limits have been hit.

104

105

Recommended action:

106

- Pause requests for a few seconds

107

- Retry with exponential backoff

108

- Consider reducing request frequency

109

"""

110

```

111

112

### Request Errors

113

114

Errors related to malformed or invalid requests.

115

116

```python { .api }

117

class HubspotBadRequest(HubspotError):

118

"""

119

400 Bad Request error.

120

121

Raised when:

122

- Request parameters are invalid or malformed

123

- Required fields are missing

124

- Data validation fails

125

- API endpoint doesn't exist

126

127

This error indicates the request needs to be corrected

128

before retrying.

129

"""

130

```

131

132

### Configuration Errors

133

134

Errors related to connector configuration and setup.

135

136

```python { .api }

137

class InvalidStartDateConfigError(Exception):

138

"""

139

Raised when invalid start_date is provided in configuration.

140

141

The start_date must be a valid ISO 8601 datetime string.

142

Common issues:

143

- Incorrect date format

144

- Invalid timezone specification

145

- Future dates that don't make sense for historical sync

146

"""

147

148

def __init__(self, actual_value: Any, message: str):

149

"""

150

Initialize with details about the invalid start_date.

151

152

Parameters:

153

- actual_value: The invalid value that was provided

154

- message: Detailed error message explaining the issue

155

"""

156

```

157

158

## Usage Examples

159

160

### Basic Error Handling

161

162

```python

163

from source_hubspot.streams import API

164

from source_hubspot.errors import (

165

HubspotInvalidAuth, HubspotRateLimited, HubspotTimeout,

166

HubspotAccessDenied, HubspotBadRequest

167

)

168

import time

169

170

api = API(credentials)

171

172

try:

173

data, response = api.get("/crm/v3/objects/contacts")

174

print(f"Retrieved {len(data.get('results', []))} contacts")

175

176

except HubspotInvalidAuth as e:

177

print(f"Authentication failed: {e}")

178

print("Please check your credentials and try again")

179

180

except HubspotAccessDenied as e:

181

print(f"Access denied: {e}")

182

print("Check your OAuth scopes or Private App permissions")

183

184

except HubspotRateLimited as e:

185

retry_after = e.response.headers.get("Retry-After", "60")

186

print(f"Rate limited. Retrying after {retry_after} seconds...")

187

time.sleep(int(retry_after))

188

# Retry the request

189

190

except HubspotTimeout as e:

191

print(f"Request timeout: {e}")

192

print("Pausing before retry...")

193

time.sleep(5)

194

# Retry with backoff

195

196

except HubspotBadRequest as e:

197

print(f"Bad request: {e}")

198

print("Check your request parameters and try again")

199

```

200

201

### Advanced Error Handling with Retry Logic

202

203

```python

204

import time

205

import random

206

from typing import Tuple, Any

207

208

def api_call_with_retry(

209

api: API,

210

endpoint: str,

211

params: dict = None,

212

max_retries: int = 3

213

) -> Tuple[Any, bool]:

214

"""

215

Make API call with comprehensive error handling and retry logic.

216

217

Returns:

218

- Tuple of (data, success)

219

"""

220

221

for attempt in range(max_retries + 1):

222

try:

223

data, response = api.get(endpoint, params)

224

return data, True

225

226

except HubspotInvalidAuth as e:

227

print(f"Authentication error: {e}")

228

return None, False # Don't retry auth errors

229

230

except HubspotAccessDenied as e:

231

print(f"Access denied: {e}")

232

return None, False # Don't retry permission errors

233

234

except HubspotRateLimited as e:

235

if attempt < max_retries:

236

retry_after = int(e.response.headers.get("Retry-After", "60"))

237

print(f"Rate limited. Waiting {retry_after} seconds before retry {attempt + 1}")

238

time.sleep(retry_after)

239

continue

240

else:

241

print("Max retries exceeded for rate limiting")

242

return None, False

243

244

except HubspotTimeout as e:

245

if attempt < max_retries:

246

# Exponential backoff with jitter

247

backoff_time = (2 ** attempt) + random.uniform(0, 1)

248

print(f"Timeout error. Retrying in {backoff_time:.1f} seconds")

249

time.sleep(backoff_time)

250

continue

251

else:

252

print("Max retries exceeded for timeout")

253

return None, False

254

255

except HubspotBadRequest as e:

256

print(f"Bad request error: {e}")

257

return None, False # Don't retry bad requests

258

259

except Exception as e:

260

print(f"Unexpected error: {e}")

261

if attempt < max_retries:

262

time.sleep(2 ** attempt)

263

continue

264

return None, False

265

266

return None, False

267

268

# Usage

269

data, success = api_call_with_retry(api, "/crm/v3/objects/contacts", {"limit": 100})

270

if success:

271

print(f"Successfully retrieved data: {len(data.get('results', []))} records")

272

else:

273

print("Failed to retrieve data after retries")

274

```

275

276

### Connection Testing with Error Handling

277

278

```python

279

from source_hubspot import SourceHubspot

280

from source_hubspot.errors import HubspotInvalidAuth, HubspotAccessDenied

281

import logging

282

283

def test_hubspot_connection(config: dict) -> Tuple[bool, str]:

284

"""

285

Test HubSpot connection with detailed error reporting.

286

287

Returns:

288

- Tuple of (is_healthy, error_message)

289

"""

290

291

try:

292

source = SourceHubspot(catalog=None, config=config, state=None)

293

logger = logging.getLogger("connection_test")

294

295

is_healthy, error = source.check_connection(logger, config)

296

297

if is_healthy:

298

return True, "Connection successful"

299

else:

300

return False, f"Connection failed: {error}"

301

302

except HubspotInvalidAuth:

303

return False, "Invalid authentication credentials. Please verify your client ID, client secret, and refresh token."

304

305

except HubspotAccessDenied:

306

return False, "Access denied. Please check your OAuth scopes or Private App permissions."

307

308

except InvalidStartDateConfigError as e:

309

return False, f"Invalid start_date configuration: {e}"

310

311

except Exception as e:

312

return False, f"Unexpected error during connection test: {e}"

313

314

# Test different credential configurations

315

oauth_config = {

316

"credentials": {

317

"credentials_title": "OAuth Credentials",

318

"client_id": "test_client_id",

319

"client_secret": "test_secret",

320

"refresh_token": "test_token"

321

},

322

"start_date": "2023-01-01T00:00:00Z"

323

}

324

325

is_healthy, message = test_hubspot_connection(oauth_config)

326

print(f"OAuth connection test: {'PASS' if is_healthy else 'FAIL'} - {message}")

327

```

328

329

### Stream-Level Error Handling

330

331

```python

332

from source_hubspot.streams import Contacts

333

334

def safe_stream_read(stream, sync_mode="full_refresh", max_errors=5):

335

"""

336

Read from stream with error handling and error counting.

337

"""

338

339

error_count = 0

340

records_processed = 0

341

342

try:

343

for record in stream.read_records(sync_mode=sync_mode):

344

try:

345

# Process record

346

records_processed += 1

347

yield record

348

349

except Exception as e:

350

error_count += 1

351

print(f"Error processing record {records_processed}: {e}")

352

353

if error_count >= max_errors:

354

print(f"Max errors ({max_errors}) exceeded. Stopping stream read.")

355

break

356

357

except HubspotRateLimited as e:

358

print(f"Stream read rate limited: {e}")

359

retry_after = e.response.headers.get("Retry-After", "60")

360

print(f"Consider pausing for {retry_after} seconds before continuing")

361

362

except HubspotInvalidAuth as e:

363

print(f"Authentication error during stream read: {e}")

364

print("Stream read cannot continue without valid authentication")

365

366

finally:

367

print(f"Stream read completed. Processed: {records_processed}, Errors: {error_count}")

368

369

# Usage

370

contacts = Contacts(api=api, start_date="2023-01-01T00:00:00Z", credentials=credentials)

371

372

for record in safe_stream_read(contacts):

373

# Process each record safely

374

contact_email = record['properties'].get('email', 'No email')

375

print(f"Contact: {contact_email}")

376

```

377

378

## Error Response Details

379

380

The HubSpot API provides detailed error information in response bodies:

381

382

```python { .api }

383

# Example error response structure

384

{

385

"status": "error",

386

"message": "This request is not allowed for this app",

387

"category": "PERMISSION_ERROR",

388

"subCategory": "SCOPE_MISSING",

389

"context": {

390

"missingScopes": ["contacts"],

391

"requiredScopes": ["contacts", "crm.objects.contacts.read"]

392

}

393

}

394

```

395

396

The connector parses these responses and includes relevant details in exception messages to help with troubleshooting.

397

398

## Best Practices

399

400

1. **Always handle authentication errors** - These require user intervention

401

2. **Implement exponential backoff** - For timeout and server errors

402

3. **Respect rate limit headers** - Use Retry-After values when available

403

4. **Log error context** - Include response details for debugging

404

5. **Differentiate retry vs non-retry errors** - Don't retry permission or configuration errors

405

6. **Monitor error patterns** - Track error rates to identify systemic issues