Bulk Operations
Bulk operations let you process up to 100 listings in a single request. Use them for initial sync and mass updates.
Why Use Bulk
| Approach | Requests for 100 listings |
|---|---|
| Individual requests | 100 |
| Bulk operation | 1 |
Bulk reduces network overhead and speeds up synchronization.
Operation Types
Each operation must contain an op field defining the action type:
| Operation | Description |
|---|---|
upsert | Create or update a listing |
link | Link external ID to an existing listing |
unlink | Unlink external ID |
delete | Delete a listing |
validate | Check data without saving |
Create or Update Listings
The upsert operation creates a new listing or updates an existing one by externalId.
const response = await fetch('https://crm.rentix.md/api/v1/listings/bulk', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
operations: [
{
op: 'upsert',
externalId: 'apt-001',
announcementType: 'rent',
propertyType: 'residential',
propertySecondaryType: 'apartment',
announcementValue: 500,
announcementCurrency: 'EUR',
announcementDescription: 'Cozy apartment in the center...',
files: [
{ url: 'https://example.com/photo1.jpg', externalFileId: 'apt-001-photo-1' },
{ url: 'https://example.com/photo2.jpg', externalFileId: 'apt-001-photo-2' },
{ url: 'https://example.com/photo3.jpg', externalFileId: 'apt-001-photo-3' }
],
announcementStatus: 'active'
},
{
op: 'upsert',
externalId: 'apt-002',
announcementType: 'rent',
propertyType: 'residential',
propertySecondaryType: 'apartment',
announcementValue: 700,
announcementCurrency: 'EUR',
announcementDescription: 'Spacious apartment with balcony...',
files: [
{ url: 'https://example.com/photo4.jpg', externalFileId: 'apt-002-photo-1' },
{ url: 'https://example.com/photo5.jpg', externalFileId: 'apt-002-photo-2' },
{ url: 'https://example.com/photo6.jpg', externalFileId: 'apt-002-photo-3' }
],
announcementStatus: 'active'
}
]
})
});
const result = await response.json();
console.log(`Succeeded: ${result.summary.succeeded}/${result.summary.total}`);
curl -X POST https://crm.rentix.md/api/v1/listings/bulk \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"op": "upsert",
"externalId": "apt-001",
"announcementStatus": "active",
"announcementType": "rent",
"propertyType": "residential",
"propertySecondaryType": "apartment",
"announcementValue": 500,
"announcementCurrency": "EUR",
"files": [
{ "url": "https://example.com/photo1.jpg", "externalFileId": "apt-001-photo-1" }
]
}
]
}'
$data = [
'operations' => [
[
'op' => 'upsert',
'externalId' => 'apt-001',
'announcementType' => 'rent',
'propertyType' => 'residential',
'propertySecondaryType' => 'apartment',
'announcementValue' => 500,
'announcementCurrency' => 'EUR',
'announcementStatus' => 'active',
'files' => [
['url' => 'https://example.com/photo1.jpg', 'externalFileId' => 'apt-001-photo-1']
]
]
]
];
$ch = curl_init('https://crm.rentix.md/api/v1/listings/bulk');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: ApiKey YOUR_API_KEY',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
{
"results": [
{
"op": "upsert",
"externalId": "apt-001",
"id": 42,
"success": true,
"created": true,
"updated": false
},
{
"op": "upsert",
"externalId": "apt-002",
"id": 43,
"success": true,
"created": true,
"updated": false
}
],
"summary": {
"total": 2,
"succeeded": 2,
"failed": 0
}
}
Link External ID
The link operation connects your CRM ID with an existing Rentix listing. Use it for listings created via the web interface.
{
"operations": [
{ "op": "link", "id": 42, "externalId": "apt-001" },
{ "op": "link", "id": 43, "externalId": "apt-002" }
]
}
{
"results": [
{ "op": "link", "id": 42, "externalId": "apt-001", "success": true, "linked": true },
{ "op": "link", "id": 43, "externalId": "apt-002", "success": true, "linked": true }
]
}
Unlink External ID
The unlink operation removes the connection between a listing and external ID.
{
"operations": [
{ "op": "unlink", "id": 42 },
{ "op": "unlink", "externalId": "apt-002" }
]
}
Delete Listings
The delete operation removes listings.
{
"operations": [
{ "op": "delete", "externalId": "apt-old-001" },
{ "op": "delete", "id": 99 }
]
}
{
"results": [
{ "op": "delete", "externalId": "apt-old-001", "success": true, "deleted": true }
]
}
Validate Data
The validate operation checks data without saving. Use it for form validation in your CRM.
{
"operations": [
{
"op": "validate",
"externalId": "apt-new",
"announcementType": "rent",
"propertyType": "residential",
"propertySecondaryType": "apartment",
"announcementValue": 500
}
]
}
{
"results": [
{
"op": "validate",
"externalId": "apt-new",
"success": true,
"valid": true,
"errors": [],
"warnings": []
}
]
}
Combine Operations
You can perform different operation types in a single request:
{
"operations": [
{ "op": "upsert", "externalId": "apt-001", "announcementValue": 550 },
{ "op": "upsert", "externalId": "apt-002", "announcementStatus": "hidden" },
{ "op": "link", "id": 99, "externalId": "apt-003" },
{ "op": "delete", "externalId": "apt-old" }
]
}
Response Status Codes
The HTTP status code of a bulk response depends on the outcome of individual operations:
| Status | Condition | Description |
|---|---|---|
| 200 | All operations succeeded | Every operation has success: true |
| 207 | Partial success | Some operations succeeded, some failed |
| 422 | All operations failed | Every operation has success: false |
Always check the summary field and individual results regardless of the HTTP status code.
Error Handling
An error in one operation doesn't stop the others. Check success for each result.
{
"results": [
{ "op": "upsert", "externalId": "apt-001", "id": 42, "success": true },
{
"op": "upsert",
"externalId": "apt-002",
"id": null,
"success": false,
"error": {
"statusCode": 400,
"body": {
"error": "propertyType is required",
"error_code": "VALIDATION_ERROR"
}
}
},
{ "op": "upsert", "externalId": "apt-003", "id": 44, "success": true }
],
"summary": {
"total": 3,
"succeeded": 2,
"failed": 1
}
}
Handle failed operations:
const result = await response.json();
const failed = result.results.filter(r => !r.success);
if (failed.length > 0) {
console.error('Errors:', failed);
// Log or retry later
}
Recommendations
Send Sequentially
Send batches sequentially, not in parallel. The system processes operations in queue order — parallel sending won't speed up processing.
Update Only Changed Data
After initial sync, send only changed listings. Resending all listings without changes is an inefficient use of the API.
Use Partial Updates
Pass only changed fields. To clear a field, send null:
{
"op": "upsert",
"externalId": "apt-001",
"announcementValue": 600,
"propertyFloorNumber": null
}
Limits
| Parameter | Value |
|---|---|
| Maximum operations per request | 100 |
Operation upsert fields | see PUT /listings |
Error Codes
| Code | Description | Solution |
|---|---|---|
VALIDATION_ERROR | Field validation error | Check required fields and formats |
USER_RESOLUTION_FAILED | Agent not found | Check userId or externalUserId |
NOT_FOUND | Listing not found | Use externalId for upsert |
EXTERNAL_ID_ALREADY_LINKED | External ID already in use | Use a unique external ID |