Media

Upload Files

How to upload photos — via URL in listing, import by URL, or signed URL

Three ways to upload photos, from simple to advanced:

  1. URL in listing — pass links directly when creating a listing
  2. Import by URL — upload photos separately, then link to listing
  3. Signed URL — for uploading files from client

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}`);
Response
{
  "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}`);
Response
{
  "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}`);
Response
{
  "uuid": "abc123",
  "upload": [
    {
      "url": "https://storage.googleapis.com/bucket/abc123_living-room.jpg?signature=...",
      "generatedFileName": "abc123_living-room.jpg",
      "externalId": "apt-001-photo-1"
    }
  ]
}
The URL is valid for 30 minutes. Upload the file before it expires.

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}`);

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:

OperationDescription
upload-from-urlUpload file by URL
confirm-uploadConfirm signed URL upload
linkLink external ID to file
unlinkUnlink external ID
deleteDelete 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:

Response with Error
{
  "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

ErrorCauseSolution
Duplicate external IDExternal ID is already in useUse a unique ID for each file
Upload URL expiredSigned URL expiredRequest a new URL
Invalid content typeInvalid file formatUse JPEG, PNG, WebP, or HEIC
File too largeFile is too bigReduce size or compress
Copyright © 2026