Concepts

Async Jobs

How to track long-running operations via Job API

Some operations take time: publishing a listing requires translating the description, processing geolocation, and optimizing photos. Instead of making you wait, the API runs these tasks in the background and returns a jobId for tracking.

When jobId Is Returned

OperationWhat Happens in Background
Publishing a listingDescription translation, location processing, photo analysis
Uploading mediaOptimization, thumbnail generation

How It Works

1. Request Returns jobId

When publishing a listing, the status changes to pending_active, and a jobId is returned:

{
  "id": 42,
  "externalId": "APT-001",
  "status": "pending_active",
  "publicUrl": "https://rentix.md/announcement/42",
  "updated": true,
  "jobId": 789
}

2. Check Job Status

Use GET /job/status to track progress:

async function waitForJob(jobId) {
  while (true) {
    const response = await fetch(
      `https://crm.rentix.md/api/v1/job/status?id=${jobId}`,
      { headers: { 'Authorization': 'ApiKey YOUR_API_KEY' } }
    );

    const job = await response.json();

    if (job.status === 'completed') {
      console.log('Job completed:', job.resultData);
      return job;
    }

    if (job.status === 'failed') {
      throw new Error(job.publicErrorMessage);
    }

    // Wait one second before the next check
    await new Promise(r => setTimeout(r, 1000));
  }
}

const job = await waitForJob(789);

3. Job Statuses

StatusDescriptionAction
pendingQueuedWait
processingRunningWait
completedSuccessCheck resultData
failedErrorCheck publicErrorMessage
failed_retryableTemporary errorCan retry the operation

Response Examples

Job Queued

{
  "id": 789,
  "type": "announcement_finalize",
  "status": "pending",
  "queuePosition": 3,
  "createdAt": "2026-02-08T12:00:00.000Z"
}

queuePosition: 3 — there are 3 jobs ahead of yours in the queue.

Job Running

{
  "id": 789,
  "type": "announcement_finalize",
  "status": "processing",
  "queuePosition": null,
  "createdAt": "2026-02-08T12:00:00.000Z"
}

Job Completed

{
  "id": 789,
  "type": "announcement_finalize",
  "status": "completed",
  "resultData": {
    "announcementId": 42,
    "completedOperations": ["translate_description", "process_location", "analyze_images"],
    "finalStatus": "active"
  },
  "createdAt": "2026-02-08T12:00:00.000Z",
  "completedAt": "2026-02-08T12:00:15.000Z"
}

After successful finalization, the listing status becomes active.

Job Failed

{
  "id": 789,
  "type": "announcement_finalize",
  "status": "failed",
  "publicErrorMessage": "Failed to process images",
  "isRetryable": true,
  "createdAt": "2026-02-08T12:00:00.000Z",
  "completedAt": "2026-02-08T12:00:10.000Z"
}

When isRetryable: true, you can retry the operation — send the same publish request.

Response Fields

FieldTypeDescription
idnumberJob ID
typestringOperation type
statusstringCurrent status
resultDataobjectResult (when completed)
publicErrorMessagestringError message
isRetryablebooleanWhether retry is possible
queuePositionnumberPosition in queue
createdAtstringCreation time
completedAtstringCompletion time

Recommendations

Polling Frequency

Don't poll status more than once per second — this creates unnecessary load and doesn't speed up execution.

Logging

Save jobId in your logs. This helps with diagnostics — support can find the job by ID.

Error Handling

Check isRetryable. If true — it's a temporary issue, you can retry. If false — you need to fix the data.

Typical Publishing Scenario

1. PUT /listings { externalId: "APT-001", announcementStatus: "active" }
   → status: "pending_active", jobId: 789

2. GET /job/status?id=789
   → status: "pending", queuePosition: 3

3. GET /job/status?id=789 (after 5 sec)
   → status: "processing"

4. GET /job/status?id=789 (after 10 sec)
   → status: "completed", resultData.finalStatus: "active"

5. Listing is published on rentix.md
Copyright © 2026