Skip to main content

Error Handling

Error codes, response format, and rate limiting.


Error Response Format

All errors follow a consistent format:

{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Human-readable error message",
"details": {
"field": "additional context"
},
"requestId": "req_abc123xyz"
}
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
detailsobjectAdditional context (optional)
requestIdstringUnique request identifier for support

HTTP Status Codes

StatusDescriptionWhen Used
200OKSuccessful GET, PATCH
201CreatedSuccessful POST creating a resource
204No ContentSuccessful DELETE
400Bad RequestInvalid input, validation errors
401UnauthorizedMissing or invalid token
403ForbiddenInsufficient permissions
404Not FoundResource doesn't exist
409ConflictResource conflict (e.g., duplicate)
422Unprocessable EntityBusiness logic validation failed
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error

Error Codes

Authentication Errors (401)

CodeMessageDescription
UNAUTHORIZEDInvalid or missing authentication tokenToken is missing, expired, or invalid
TOKEN_EXPIREDAuthentication token has expiredJWT has passed its expiration time
TOKEN_INVALIDAuthentication token is malformedJWT cannot be decoded or verified

Authorization Errors (403)

CodeMessageDescription
FORBIDDENInsufficient permissionsUser lacks required scope
TENANT_MISMATCHResource belongs to different tenantCross-tenant access attempted
SCOPE_REQUIREDMissing required scope: {scope}Specific scope needed

Not Found Errors (404)

CodeMessageDescription
CUSTOMER_NOT_FOUNDCustomer not foundCustomer ID doesn't exist
SEGMENT_NOT_FOUNDSegment not foundSegment ID doesn't exist
CAMPAIGN_NOT_FOUNDCampaign not foundCampaign ID doesn't exist
JOURNEY_NOT_FOUNDJourney not foundJourney ID doesn't exist
JOURNEY_STEP_NOT_FOUNDJourney step not foundStep ID doesn't exist
ENROLLMENT_NOT_FOUNDEnrollment not foundEnrollment doesn't exist
CONNECTION_NOT_FOUNDSocial connection not foundConnection ID doesn't exist
POST_NOT_FOUNDSocial post not foundPost ID doesn't exist
PROMOTION_NOT_FOUNDEvent promotion not foundPromotion ID doesn't exist
NOTE_NOT_FOUNDNote not foundNote ID doesn't exist

Validation Errors (400)

CodeMessageDescription
VALIDATION_ERRORValidation failedGeneral validation failure
INVALID_EMAILInvalid email formatEmail doesn't match pattern
INVALID_PHONEInvalid phone formatPhone not in E.164 format
INVALID_DATEInvalid date formatDate not in ISO 8601 format
INVALID_CRITERIAInvalid segment criteriaCriteria JSON is malformed
MISSING_REQUIRED_FIELDMissing required field: {field}Required field not provided
INVALID_ENUM_VALUEInvalid value for {field}Value not in allowed enum

Business Logic Errors (422)

CodeMessageDescription
INVALID_CAMPAIGN_STATUSInvalid status transitionCan't transition campaign state
INVALID_JOURNEY_STATUSInvalid status transitionCan't transition journey state
JOURNEY_NO_STEPSJourney has no stepsCan't activate journey without steps
JOURNEY_ACTIVECannot modify active journeyJourney must be paused first
CAMPAIGN_ALREADY_SENTCampaign has already been sentCan't modify completed campaign
SEGMENT_IN_USESegment is used by active campaignsCan't delete segment
CUSTOMER_ALREADY_ENROLLEDCustomer already enrolled in journeyRe-entry not allowed
MERGE_SAME_CUSTOMERCannot merge customer with itselfWinner and loser are same
DUPLICATE_EMAILEmail already existsEmail uniqueness violation
OAUTH_FAILEDOAuth authorization failedOAuth flow error

Social Media Errors (500/422)

CodeMessageDescription
SOCIAL_PUBLISH_FAILEDFailed to publish to platformPlatform API error
SOCIAL_TOKEN_EXPIREDSocial media token expiredNeed to reconnect
SOCIAL_PERMISSION_DENIEDMissing platform permissionReconnect with permissions
SOCIAL_RATE_LIMITEDPlatform rate limit exceededTry again later
SOCIAL_CONTENT_REJECTEDContent rejected by platformPolicy violation

Rate Limit Errors (429)

CodeMessageDescription
RATE_LIMITEDToo many requestsRate limit exceeded
QUOTA_EXCEEDEDMonthly quota exceededUsage limit reached

Rate Limits

Default Limits

Endpoint TypeLimitWindow
Read operations1000per minute
Write operations100per minute
Campaign execution10per minute
Social publishing30per minute
Bulk operations10per minute
GraphQL queries500per minute

Rate Limit Headers

All responses include rate limit headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1734184800
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when limit resets

Rate Limit Response

When rate limited, you'll receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1734184800

{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please retry after 30 seconds.",
"details": {
"retryAfter": 30
},
"requestId": "req_abc123"
}
}

Error Handling Best Practices

Retry Strategy

For transient errors (5xx, 429), implement exponential backoff:

async function requestWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status === 429) {
const retryAfter = error.headers['retry-after'] || Math.pow(2, attempt);
await sleep(retryAfter * 1000);
continue;
}
if (error.status >= 500 && attempt < maxRetries - 1) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
throw error;
}
}
}

Handling Validation Errors

Validation errors include details about which fields failed:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"errors": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "phoneNumber", "message": "Must be E.164 format" }
]
},
"requestId": "req_abc123"
}
}

Request ID for Support

Always log the requestId from error responses. This ID can be used when contacting support to trace the specific request.


GraphQL Errors

GraphQL errors follow the GraphQL specification:

{
"data": null,
"errors": [
{
"message": "Customer not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["customer"],
"extensions": {
"code": "CUSTOMER_NOT_FOUND",
"requestId": "req_abc123"
}
}
]
}

Partial success is possible - some fields may resolve while others error:

{
"data": {
"customer": {
"id": "123",
"name": "John",
"segment": null
}
},
"errors": [
{
"message": "Segment not found",
"path": ["customer", "segment"],
"extensions": {
"code": "SEGMENT_NOT_FOUND"
}
}
]
}