Errors
Error Handling
HTTP status codes, rate limiting, retry strategies, and common errors
The API uses standard HTTP codes to signal errors. All errors return JSON with a problem description.
Error Format
{
"statusCode": 400,
"message": "Error description",
"error": "Bad Request"
}
For validation errors, message may be an array:
{
"statusCode": 400,
"message": [
"announcementDescription must be at least 40 characters",
"propertyType must be one of: residential, commercial, parking"
],
"error": "Bad Request"
}
HTTP Status Codes
| Code | Type | Description | Retry? |
|---|---|---|---|
| 400 | Bad Request | Invalid request parameters | No — fix the data |
| 401 | Unauthorized | API key issue | No — check the key |
| 403 | Forbidden | No access to resource | No — check permissions |
| 404 | Not Found | Resource not found | No — check ID |
| 415 | Unsupported Media Type | Wrong Content-Type | No — use application/json |
| 422 | Unprocessable Entity | All bulk operations failed | No — fix the data |
| 429 | Too Many Requests | Rate limit exceeded | Yes — after pause |
| 500 | Server Error | Internal error | Yes — with exponential backoff |
Request Requirements
Content-Type
All requests with a body (POST, PUT, PATCH) must include the Content-Type: application/json header. Requests with other content types receive a 415 Unsupported Media Type response.
Body Size Limit
Request body size is limited to 512 KB. Requests exceeding this limit receive a 413 Payload Too Large response.
Rate Limiting
The API limits request count to protect against overload. Each response includes headers with limit information.
| Endpoint type | Limit |
|---|---|
| Global (all endpoints) | 300 requests / minute |
| Bulk endpoints | 30 requests / minute |
Limit Headers
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295
X-RateLimit-Reset: 1705330800
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per period |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | When limit resets (Unix timestamp) |
What to Do on 429
When limit is exceeded, API returns:
{
"statusCode": 429,
"message": "Too Many Requests"
}
Wait until the time from X-RateLimit-Reset and retry:
Node.js
async function fetchWithRateLimit(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitMs = (parseInt(resetTime) * 1000) - Date.now();
await new Promise(r => setTimeout(r, Math.max(waitMs, 1000)));
return fetch(url, options); // Retry
}
return response;
}
Retry Strategy
When to Retry Requests
| Situation | Action |
|---|---|
| 5xx errors | Retry with exponential backoff |
| 429 errors | Wait until X-RateLimit-Reset |
| Network errors | Retry with backoff |
| 4xx errors | Don't retry — fix the request |
Exponential Backoff
For server errors, increase the pause between attempts:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Server error — retry
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
// Exponential backoff: 1s, 2s, 4s
const delay = 1000 * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
}
}
}
// Usage
const response = await fetchWithRetry(
'https://crm.rentix.md/api/v1/listings',
{
method: 'PUT',
headers: { 'Authorization': 'ApiKey YOUR_API_KEY' },
body: JSON.stringify(data)
}
);
function fetchWithRetry($url, $options, $maxRetries = 3) {
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
$ch = curl_init($url);
curl_setopt_array($ch, $options);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 500) {
return ['code' => $httpCode, 'body' => $response];
}
if ($attempt < $maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
sleep(pow(2, $attempt));
}
}
throw new Exception("Max retries exceeded");
}
Common Errors
Authentication (400, 401, 403)
| Error | Cause | Solution |
|---|---|---|
Multiple authentication methods provided | Both Authorization and X-API-Key headers sent | Use only one authentication header |
API key is required | No API key provided | Add Authorization: ApiKey YOUR_KEY header |
Invalid API key | API key is incorrect or revoked | Check the key value |
Agency is suspended | Agency account is suspended | Contact support |
CRM is not enabled for this agency | CRM access not enabled | Contact support to enable CRM |
Listings (400)
| Error | Cause | Solution |
|---|---|---|
Cannot provide both id and externalId | Both identifiers were passed | Use only one |
Must provide id or externalId | No identifier provided for update | Add id or externalId |
External ID already linked | External ID is used by another listing | Use a unique ID |
Minimum 3 photos required | Photos needed for publishing | Upload at least 3 photos |
Description too short | Description is less than 40 characters | Add more text |
Media (400)
| Error | Cause | Solution |
|---|---|---|
Duplicate external ID | External ID is already in use | Use a unique ID |
File not found | File doesn't exist | Check fileId or externalFileId |
Invalid file type | Unsupported format | Use JPEG, PNG, WebP, or HEIC |
Upload URL expired | Signed URL expired (30 minutes) | Request a new URL |
Field Validation
| Code | Description |
|---|---|
STRING_TOO_SHORT | String is shorter than minimum |
STRING_TOO_LONG | String is longer than maximum |
NUMBER_TOO_SMALL | Number is less than minimum |
NUMBER_TOO_LARGE | Number is greater than maximum |
INVALID_ENUM_VALUE | Value is not in allowed list |
REQUIRED_FIELD | Required field is empty |
Example: Handle All Cases
Node.js
async function apiRequest(endpoint, data) {
const url = `https://crm.rentix.md/api/v1${endpoint}`;
const options = {
method: 'PUT',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
for (let attempt = 0; attempt < 3; attempt++) {
const response = await fetch(url, options);
// Success
if (response.ok) {
return response.json();
}
// Rate limit — wait and retry
if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitMs = (parseInt(resetTime) * 1000) - Date.now();
await new Promise(r => setTimeout(r, Math.max(waitMs, 1000)));
continue;
}
// Server error — retry with backoff
if (response.status >= 500) {
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
continue;
}
// Client error — don't retry
const error = await response.json();
throw new Error(`API Error: ${JSON.stringify(error.message)}`);
}
throw new Error('Max retries exceeded');
}
Recommendations
- Log errors — save
statusCode,message, and timestamp for diagnostics - Don't ignore 4xx — these are errors in your data, fix them
- Respect rate limits — on 429, always wait the specified time
- Use exponential backoff — on 5xx, don't hammer the server immediately