Skip to main content

Overview

The Alta Multes API is designed to support offline operation for field agents. Devices can work without an internet connection by pre-requesting ranges of expedition numbers, allowing fines to be created offline and synchronized later.

How Offline Mode Works

Each fine is uniquely identified by:
  • Municipality code (Cdclie)
  • Expedition number (Cdexpa)
Since each device (IMEI) has its own expedition number range, multiple devices can work offline simultaneously without conflicts.

Requesting Expedition Ranges

Use the ObtenirRang endpoint to request expedition number ranges for offline work.

Endpoint

Request
GET https://pdaprv16.orgt.diba.cat/RestMultesPDA/svcMultesPDA.svc/rest/ObtenirRang?
  pImei=353947020131525&pEstat1=1&pEstat2=1

Parameters

ParameterTypeRequiredDescription
pImeistringYesDevice identifier (max 15 alphanumeric characters)
pEstat1integerYesSet to 1 to request primary range, 0 to skip
pEstat2integerYesSet to 1 to request secondary range, 0 to skip
Request both ranges (pEstat1=1 and pEstat2=1) to ensure your device has sufficient expedition numbers for extended offline periods.

Response

Example Response
<Rang xmlns="http://schemas.datacontract.org/2004/07/WcfMultesPDA">
  <Retorn>
    <CodiRetorn>0</CodiRetorn>
    <DescRetorn/>
  </Retorn>
  <Minrang1>1590621</Minrang1>
  <MaxRang1>1590630</MaxRang1>
  <MinRang2>1590631</MinRang2>
  <MaxRang2>1590640</MaxRang2>
</Rang>
Fields:
  • Minrang1 / MaxRang1 - Primary range (10 expedition numbers: 1590621-1590630)
  • MinRang2 / MaxRang2 - Secondary range (10 expedition numbers: 1590631-1590640)
  • CodiRetorn - Return code (0 = success, -1 = application error, -9000 = exception)

Return Codes

CodeMeaning
0Success - ranges assigned
-1Application error (e.g., device not found)
-9000Uncontrolled exception

Managing Expedition Numbers

1

Request ranges before going offline

Call ObtenirRang when the device has connectivity to obtain expedition number ranges.
2

Store ranges locally

Save the expedition number ranges in the device’s local storage or database.
3

Assign numbers sequentially

As agents create fines, assign expedition numbers from the range sequentially (start with Minrang1).
4

Monitor range depletion

Track how many numbers remain. Request new ranges before exhausting current ones.
5

Sync when online

When connectivity returns, submit all offline-created fines to the API using AltaMulta.

Example Implementation

JavaScript Example
class ExpeditionRangeManager {
  constructor() {
    this.ranges = { primary: null, secondary: null };
    this.currentRange = null;
    this.nextNumber = null;
  }

  async requestRanges(imei) {
    const response = await fetch(
      `https://pdaprv16.orgt.diba.cat/RestMultesPDA/svcMultesPDA.svc/rest/ObtenirRang?` +
      `pImei=${imei}&pEstat1=1&pEstat2=1`,
      { /* certificate config */ }
    );
    
    const data = await this.parseXmlResponse(response);
    
    if (data.Retorn.CodiRetorn === 0) {
      this.ranges.primary = {
        min: parseInt(data.Minrang1),
        max: parseInt(data.MaxRang1)
      };
      this.ranges.secondary = {
        min: parseInt(data.MinRang2),
        max: parseInt(data.MaxRang2)
      };
      this.currentRange = this.ranges.primary;
      this.nextNumber = this.ranges.primary.min;
      
      // Persist to local storage
      localStorage.setItem('expeditionRanges', JSON.stringify(this.ranges));
    }
  }

  getNextExpeditionNumber() {
    if (!this.currentRange || this.nextNumber > this.currentRange.max) {
      // Switch to secondary range or throw error
      if (this.currentRange === this.ranges.primary && this.ranges.secondary) {
        this.currentRange = this.ranges.secondary;
        this.nextNumber = this.ranges.secondary.min;
      } else {
        throw new Error('Expedition numbers exhausted. Please sync and request new ranges.');
      }
    }
    
    const number = this.nextNumber;
    this.nextNumber++;
    
    // Persist current state
    localStorage.setItem('nextExpeditionNumber', this.nextNumber);
    
    return number.toString();
  }

  getRemainingCount() {
    let remaining = 0;
    if (this.currentRange === this.ranges.primary) {
      remaining += (this.ranges.primary.max - this.nextNumber + 1);
      if (this.ranges.secondary) {
        remaining += (this.ranges.secondary.max - this.ranges.secondary.min + 1);
      }
    } else if (this.currentRange === this.ranges.secondary) {
      remaining += (this.ranges.secondary.max - this.nextNumber + 1);
    }
    return remaining;
  }
}
Python Example
class ExpeditionRangeManager:
    def __init__(self):
        self.ranges = {'primary': None, 'secondary': None}
        self.current_range = None
        self.next_number = None
    
    def request_ranges(self, imei, cert_path, key_path):
        import requests
        
        response = requests.get(
            'https://pdaprv16.orgt.diba.cat/RestMultesPDA/svcMultesPDA.svc/rest/ObtenirRang',
            params={'pImei': imei, 'pEstat1': 1, 'pEstat2': 1},
            cert=(cert_path, key_path)
        )
        
        # Parse XML response
        data = self.parse_xml_response(response.text)
        
        if data['CodiRetorn'] == 0:
            self.ranges['primary'] = {
                'min': int(data['Minrang1']),
                'max': int(data['MaxRang1'])
            }
            self.ranges['secondary'] = {
                'min': int(data['MinRang2']),
                'max': int(data['MaxRang2'])
            }
            self.current_range = self.ranges['primary']
            self.next_number = self.ranges['primary']['min']
            
            # Persist to database or file
            self.save_ranges()
    
    def get_next_expedition_number(self):
        if not self.current_range or self.next_number > self.current_range['max']:
            # Switch to secondary range
            if self.current_range == self.ranges['primary'] and self.ranges['secondary']:
                self.current_range = self.ranges['secondary']
                self.next_number = self.ranges['secondary']['min']
            else:
                raise Exception('Expedition numbers exhausted')
        
        number = self.next_number
        self.next_number += 1
        self.save_state()
        
        return str(number)

Offline Fine Creation

When working offline, fines are created locally and queued for synchronization:

Workflow

1

Create fine locally

Agent fills out fine details in the offline app. Assign an expedition number from the available range.
2

Store in local queue

Save the fine data to a local database or queue (SQLite, IndexedDB, etc.).
3

Mark as pending sync

Flag the fine as “not synchronized” in your local database.
4

Wait for connectivity

Continue working offline. Multiple fines can be queued.

Data to Store Locally

Ensure your offline storage includes all required fields for the AltaMulta endpoint:
Example Local Fine Record
{
  "localId": "uuid-1234",
  "synchronized": false,
  "expeditionNumber": "1590621",
  "municipalityCode": "088",
  "fineData": {
    "Cdagen": "220",
    "Cdclie": "088",
    "Cdexpa": "1590621",
    "Cdmatr": "1234ABC",
    "Dtinfr": "20260304",
    "Hmmult": "1045",
    "Sqcond": "8430",
    "Immult": "600",
    "Imppag": "300",
    "Quiden": "A"
    // ... other fields
  },
  "createdAt": "2026-03-04T10:45:00Z"
}

Synchronization

When connectivity is restored:
1

Check connectivity

Verify internet connection is stable before starting sync.
2

Retrieve pending fines

Query local database for fines marked as “not synchronized”.
3

Submit to API

Call AltaMulta for each pending fine, ensuring XML fields are alphabetically ordered.
4

Handle responses

Check each response’s CodiRetorn. Mark successful submissions as synchronized.
5

Retry failures

For failed submissions, implement retry logic with exponential backoff.
6

Request new ranges

After successful sync, if ranges are depleted or low, request new ranges.
Important Synchronization Notes:
  • Ensure XML fields in AltaMulta requests are alphabetically ordered
  • The first field must be cdagen and the last must be viapen
  • See the AltaMulta example XML for correct ordering

Best Practices

Range Management

Pre-fetch Ranges

Request ranges at the start of each shift, not just when exhausted

Monitor Thresholds

Set alerts when 20% of expedition numbers remain

Dual Ranges

Always request both primary and secondary ranges for redundancy

Persist State

Save current expedition number after each fine to survive app crashes

Sync Strategy

Recommended sync timing:
  • Automatic sync when app detects connectivity
  • Manual sync button for user-initiated uploads
  • Background sync every 15-30 minutes when online
  • End-of-shift mandatory sync before device shutdown

Error Handling

Example: Retry Logic
async function syncFine(fine, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await submitFine(fine);
      const result = parseXmlResponse(response);
      
      if (result.CodiRetorn === 0) {
        markAsSynchronized(fine.localId);
        return { success: true };
      } else {
        console.error(`Fine ${fine.expeditionNumber} failed: ${result.DescRetorn}`);
        return { success: false, error: result.DescRetorn };
      }
    } catch (error) {
      if (attempt === maxRetries) {
        return { success: false, error: error.message };
      }
      // Exponential backoff: 2s, 4s, 8s
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}
Offline mode is essential for field agents who may work in areas with poor connectivity. Robust range management and sync logic ensure data integrity and uninterrupted operations.

Build docs developers (and LLMs) love