Upload Files
Three ways to upload photos, from simple to advanced:
- URL in listing — pass links directly when creating a listing
- Import by URL — upload photos separately, then link to listing
- Signed URL — for uploading files from client
URL in Listing (Recommended)
The simplest way — pass photo URLs directly when creating or updating a listing. Adding externalFileId is recommended for tracking:
{
"externalId": "apt-001",
"announcementStatus": "active",
"announcementType": "rent",
"propertyType": "residential",
"propertySecondaryType": "apartment",
"announcementValue": 500,
"files": [
{ "url": "https://placehold.co/1920x1080/jpg?text=Living+Room", "externalFileId": "apt-001-photo-1" },
{ "url": "https://placehold.co/1920x1080/jpg?text=Bedroom", "externalFileId": "apt-001-photo-2" },
{ "url": "https://placehold.co/1920x1080/jpg?text=Kitchen", "externalFileId": "apt-001-photo-3" }
]
}
The system automatically downloads, optimizes, and links the photos to the listing. On repeat requests with the same externalFileId, the system uses the already uploaded file, avoiding duplication.
Import by URL
Use this method if you want to upload photos in advance or manage them separately from listings.
Upload Single Photo
const response = await fetch('https://crm.rentix.md/api/v1/media/upload-from-url', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://placehold.co/1920x1080/jpg?text=Living+Room',
externalId: 'apt-001-photo-1'
})
});
const result = await response.json();
console.log(`Media ID: ${result.mediaId}, Status: ${result.status}`);
curl -X POST https://crm.rentix.md/api/v1/media/upload-from-url \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://placehold.co/1920x1080/jpg?text=Living+Room",
"externalId": "apt-001-photo-1"
}'
$data = [
'url' => 'https://placehold.co/1920x1080/jpg?text=Living+Room',
'externalId' => 'apt-001-photo-1'
];
$ch = curl_init('https://crm.rentix.md/api/v1/media/upload-from-url');
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);
$result = json_decode($response, true);
{
"mediaId": 123,
"status": "pending",
"externalId": "apt-001-photo-1",
"jobId": 456
}
Status pending means the file is queued for download and optimization. The jobId field lets you track processing status.
Bulk Upload
For uploading multiple files, use the bulk endpoint with upload-from-url operation:
const response = await fetch('https://crm.rentix.md/api/v1/media/bulk', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
operations: [
{ op: 'upload-from-url', url: 'https://placehold.co/1920x1080/jpg?text=Photo+1', externalId: 'apt-001-photo-1' },
{ op: 'upload-from-url', url: 'https://placehold.co/1920x1080/jpg?text=Photo+2', externalId: 'apt-001-photo-2' },
{ op: 'upload-from-url', url: 'https://placehold.co/1920x1080/jpg?text=Photo+3', externalId: 'apt-001-photo-3' }
]
})
});
const result = await response.json();
console.log(`Uploaded: ${result.summary.succeeded} of ${result.summary.total}`);
curl -X POST https://crm.rentix.md/api/v1/media/bulk \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{ "op": "upload-from-url", "url": "https://placehold.co/1920x1080/jpg?text=Photo+1", "externalId": "apt-001-photo-1" },
{ "op": "upload-from-url", "url": "https://placehold.co/1920x1080/jpg?text=Photo+2", "externalId": "apt-001-photo-2" }
]
}'
$data = [
'operations' => [
['op' => 'upload-from-url', 'url' => 'https://placehold.co/1920x1080/jpg?text=Photo+1', 'externalId' => 'apt-001-photo-1'],
['op' => 'upload-from-url', 'url' => 'https://placehold.co/1920x1080/jpg?text=Photo+2', 'externalId' => 'apt-001-photo-2']
]
];
$ch = curl_init('https://crm.rentix.md/api/v1/media/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": "upload-from-url", "externalId": "apt-001-photo-1", "mediaId": 123, "success": true },
{ "op": "upload-from-url", "externalId": "apt-001-photo-2", "mediaId": 124, "success": true }
],
"summary": { "total": 2, "succeeded": 2, "failed": 0 }
}
Signed URL
Use this method for uploading files directly from the client or when files aren't available via public URL.
Step 1. Request Upload URL
const response = await fetch('https://crm.rentix.md/api/v1/media/request-upload-url', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
files: [
{ fileName: 'living-room.jpg', size: 245000, contentType: 'image/jpeg', externalId: 'apt-001-photo-1' }
]
})
});
const result = await response.json();
console.log(`Upload URL: ${result.upload[0].url}`);
curl -X POST https://crm.rentix.md/api/v1/media/request-upload-url \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": [
{ "fileName": "living-room.jpg", "size": 245000, "contentType": "image/jpeg", "externalId": "apt-001-photo-1" }
]
}'
$data = [
'files' => [
['fileName' => 'living-room.jpg', 'size' => 245000, 'contentType' => 'image/jpeg', 'externalId' => 'apt-001-photo-1']
]
];
$ch = curl_init('https://crm.rentix.md/api/v1/media/request-upload-url');
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);
$result = json_decode($response, true);
{
"uuid": "abc123",
"upload": [
{
"url": "https://storage.googleapis.com/bucket/abc123_living-room.jpg?signature=...",
"generatedFileName": "abc123_living-room.jpg",
"externalId": "apt-001-photo-1"
}
]
}
Step 2. Upload File
Send the file via PUT request to the received URL:
curl -X PUT "https://storage.googleapis.com/bucket/abc123_living-room.jpg?signature=..." \
-H "Content-Type: image/jpeg" \
--data-binary @living-room.jpg
Step 3. Confirm Upload
Use the bulk endpoint with confirm-upload operation:
const response = await fetch('https://crm.rentix.md/api/v1/media/bulk', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
operations: [
{
op: 'confirm-upload',
uuid: 'abc123',
generatedFileName: 'abc123_living-room.jpg',
externalId: 'apt-001-photo-1'
}
]
})
});
const result = await response.json();
console.log(`Confirmed: ${result.summary.succeeded} of ${result.summary.total}`);
curl -X POST https://crm.rentix.md/api/v1/media/bulk \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"op": "confirm-upload",
"uuid": "abc123",
"generatedFileName": "abc123_living-room.jpg",
"externalId": "apt-001-photo-1"
}
]
}'
$data = [
'operations' => [
[
'op' => 'confirm-upload',
'uuid' => 'abc123',
'generatedFileName' => 'abc123_living-room.jpg',
'externalId' => 'apt-001-photo-1'
]
]
];
$ch = curl_init('https://crm.rentix.md/api/v1/media/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);
$result = json_decode($response, true);
Link Photos to Listing
After upload, link photos to a listing. There are three ways to specify a file:
By Internal ID
{
"files": [
{ "id": 123 },
{ "id": 124 }
]
}
By External ID
{
"files": [
{ "externalFileId": "apt-001-photo-1" },
{ "externalFileId": "apt-001-photo-2" }
]
}
By URL (Auto-import)
{
"files": [
{ "url": "https://placehold.co/1920x1080/jpg?text=Photo+1", "externalFileId": "apt-001-photo-1" },
{ "url": "https://placehold.co/1920x1080/jpg?text=Photo+2", "externalFileId": "apt-001-photo-2" }
]
}
Bulk Media Operations
All media operations can be performed through a single endpoint POST /media/bulk:
| Operation | Description |
|---|---|
upload-from-url | Upload file by URL |
confirm-upload | Confirm signed URL upload |
link | Link external ID to file |
unlink | Unlink external ID |
delete | Delete file |
Combined Operations Example
{
"operations": [
{ "op": "upload-from-url", "url": "https://example.com/new.jpg", "externalId": "new-photo" },
{ "op": "link", "mediaId": 123, "externalId": "old-photo-linked" },
{ "op": "delete", "externalId": "old-photo-to-remove" }
]
}
Error Handling
An error in one operation doesn't stop the others. Check success for each result:
{
"results": [
{ "op": "upload-from-url", "externalId": "photo-1", "mediaId": 123, "success": true },
{
"op": "upload-from-url",
"externalId": "photo-2",
"mediaId": null,
"success": false,
"error": {
"statusCode": 400,
"body": {
"error": "Duplicate external ID",
"error_code": "DUPLICATE_EXTERNAL_ID"
}
}
}
],
"summary": { "total": 2, "succeeded": 1, "failed": 1 }
}
Common Errors
| Error | Cause | Solution |
|---|---|---|
Duplicate external ID | External ID is already in use | Use a unique ID for each file |
Upload URL expired | Signed URL expired | Request a new URL |
Invalid content type | Invalid file format | Use JPEG, PNG, WebP, or HEIC |
File too large | File is too big | Reduce size or compress |