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
GET https://pdaprv16.orgt.diba.cat/RestMultesPDA/svcMultesPDA.svc/rest/ObtenirRang?
pImei = 353947020131525 & pEstat1 = 1 & pEstat2 = 1
Parameters
Parameter Type Required Description pImeistring Yes Device identifier (max 15 alphanumeric characters) pEstat1integer Yes Set to 1 to request primary range, 0 to skip pEstat2integer Yes Set 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
< 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
Code Meaning 0Success - ranges assigned -1Application error (e.g., device not found) -9000Uncontrolled exception
Managing Expedition Numbers
Request ranges before going offline
Call ObtenirRang when the device has connectivity to obtain expedition number ranges.
Store ranges locally
Save the expedition number ranges in the device’s local storage or database.
Assign numbers sequentially
As agents create fines, assign expedition numbers from the range sequentially (start with Minrang1).
Monitor range depletion
Track how many numbers remain. Request new ranges before exhausting current ones.
Sync when online
When connectivity returns, submit all offline-created fines to the API using AltaMulta.
Example Implementation
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 ;
}
}
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
Create fine locally
Agent fills out fine details in the offline app. Assign an expedition number from the available range.
Store in local queue
Save the fine data to a local database or queue (SQLite, IndexedDB, etc.).
Mark as pending sync
Flag the fine as “not synchronized” in your local database.
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:
Check connectivity
Verify internet connection is stable before starting sync.
Retrieve pending fines
Query local database for fines marked as “not synchronized”.
Submit to API
Call AltaMulta for each pending fine, ensuring XML fields are alphabetically ordered.
Handle responses
Check each response’s CodiRetorn. Mark successful submissions as synchronized.
Retry failures
For failed submissions, implement retry logic with exponential backoff.
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
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.