Upsert Pattern
The API uses the upsert pattern: a single PUT request creates a new object or updates an existing one. You don't need to check if the object exists — the API handles it automatically.
Why This Matters
In a classic REST API, syncing data requires:
1. GET /listings/external/APT-001 # Check existence
2. If 404 → POST /listings # Create
If 200 → PUT /listings/42 # Update
With the upsert pattern, it's simpler:
1. PUT /listings { externalId: "APT-001", ... }
# API creates or updates automatically
How It Works
| Situation | What Happens |
|---|---|
| Object not found | A new one is created |
| Object found | The existing one is updated |
Lookup happens by id or externalId — whichever you passed in the request.
Example: Creation
The first request with externalId: "APT-001" creates a listing:
curl -X PUT https://crm.rentix.md/api/v1/listings \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "APT-001",
"announcementType": "rent",
"propertyType": "residential",
"propertySecondaryType": "apartment",
"announcementValue": 500,
"announcementCurrency": "EUR"
}'
{
"id": 42,
"externalId": "APT-001",
"status": "draft",
"created": true,
"updated": false
}
Note: created: true — the object was created.
Example: Update
A repeat request with the same externalId updates the listing:
curl -X PUT https://crm.rentix.md/api/v1/listings \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "APT-001",
"announcementValue": 600
}'
{
"id": 42,
"externalId": "APT-001",
"status": "draft",
"created": false,
"updated": true
}
Now updated: true — the object was updated.
Partial Updates
When updating, pass only the changed fields. Others keep their values:
// First request — full creation
{
"externalId": "APT-001",
"announcementType": "rent",
"announcementValue": 500,
"propertyArea": 65,
"propertyFloorNumber": 3
}
// Second request — change only the price
{
"externalId": "APT-001",
"announcementValue": 600
}
// propertyArea and propertyFloorNumber stay the same
Clearing Fields
To clear a field (remove its value), send null:
{
"externalId": "APT-001",
"propertyFloorNumber": null
}
After this request, propertyFloorNumber will be empty.
Benefits
Simple Integration
One endpoint for all cases. No need to write existence-check logic.
Idempotency
A repeat request with the same data won't create a duplicate — it just updates the existing object. This is important for network failures: you can safely retry requests.
Atomicity
Creation or update happens in a single transaction. There's no race condition like "checked — wasn't there, created — duplicate".
Bulk Upsert
The same principle works for bulk operations. Up to 100 objects per request. Each operation must include an op field:
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", "announcementValue": 500 },
{ "op": "upsert", "externalId": "APT-002", "announcementValue": 700 },
{ "op": "upsert", "externalId": "APT-003", "announcementValue": 450 }
]
}'
{
"results": [
{ "op": "upsert", "externalId": "APT-001", "id": 42, "success": true, "created": false, "updated": true },
{ "op": "upsert", "externalId": "APT-002", "id": 43, "success": true, "created": true, "updated": false },
{ "op": "upsert", "externalId": "APT-003", "id": 44, "success": true, "created": true, "updated": false }
],
"summary": { "total": 3, "succeeded": 3, "failed": 0 }
}
Limitations
id and externalId at the same time — this will cause an error. Choose one identification method.| Resource | Endpoint |
|---|---|
| Listings | PUT /listings |
| Users | PUT /users |