Using the API to import data

Geoff 21 days ago

Hi,

I have some data I'd like to import into Traccar, and I'll be using the API to do it. (I love having an API to do stuff like this - thanks for providing it!)

I'm comfortable with the docs describing the API calls but I'm new to Traccar and I'm having difficulty understanding the vocabulary and data model.

To be concrete: I have a couple of million records like this, and I'd like to import them into Traccar with the best fidelity I can.

    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          171.725274,
          -43.161877
        ]
      },
      "properties": {
        "battery_status": null,
        "ping": null,
        "battery": null,
        "tracker_id": "google-maps-timeline-export",
        "topic": "Google Maps Timeline Export",
        "altitude": null,
        "longitude": "171.725274",
        "velocity": null,
        "trigger": null,
        "bssid": null,
        "ssid": null,
        "connection": null,
        "vertical_accuracy": null,
        "accuracy": null,
        "timestamp": 1444863716,
        "latitude": "-43.161877",
        "mode": null,
        "inrids": [],
        "in_regions": [],
        "city": null,
        "country": "New Zealand",
        "geodata": {
          "type": "Feature",
          "geometry": {
            "type": "Point",
            "coordinates": [
              171.7238527,
              -43.159522
            ]
          },
          "properties": {
            "name": "Proposed Sidle 73 extension from Dixons to Craigieburn Campground",
            "type": "house",
            "state": "Canterbury",
            "county": "Selwyn District",
            "extent": [
              171.7186489,
              -43.1528212,
              171.7316202,
              -43.1670156
            ],
            "osm_id": 284390097,
            "street": "Broken River Road",
            "country": "New Zealand",
            "osm_key": "highway",
            "osm_type": "W",
            "osm_value": "proposed",
            "countrycode": "NZ"
          }
        },
        "course": null,
        "course_accuracy": null,
        "external_track_id": null,
        "track_id": null,
        "country_name": null
      }
    }

(They're not all Google Timeline exports - they are all Dawarich exports in 'GeoJSON' format.)

There are some obvious bits to capture, like latitude, longitude, altitude, and timestamp. I'd also like to capture some less-obvious bits like country, tracker_id, and topic. I'm not sure about the geodata - I plan on installing traccar-geocoder soon, maybe that will be sufficient?

I guess I need to create a Device to import the positions into. I can do that, but I can't see a way in the REST API to store each of the locations. Do I use the OsmAnd (https://www.traccar.org/osmand/) JSON format and post to the root of the site? Is there a better way?

And what do I do about the data I want to capture that isn't in the OsmAnd format? Do I create them as 'attributes' somewhere? Or do I put them in the OsmAnd 'extras' property?

I'd really appreciate any insights or advice here.

Many thanks.

Anton Tananaev 21 days ago

Using OsmAnd format is probably the easiest option. You can use "query/form" format. There you can include any arbitrary data beyond basic location. It will be stored in attributes.

Geoff 16 days ago

Thanks very much! I don't need to do the import now, but I thought I'd share some notes on what I got working. I couldn't find much sample code so maybe this will help someone start in the future.

First I started a test Traccar instance. I created a basic config file so it output messages to the console:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>
<properties>

    <!-- Documentation: https://www.traccar.org/configuration-file/ -->

    <entry key='database.driver'>org.h2.Driver</entry>
    <entry key='database.url'>jdbc:h2:./data/database</entry>
    <entry key='database.user'>sa</entry>
    <entry key='database.password' />

    <entry key='logger.console'>true</entry>
    <entry key='logger.enable'>true</entry>
    <!-- Available options: off, severe, warning, info, config, fine, finer, finest, all. -->
    <entry key='logger.level'>info</entry>

</properties>

Then I could run a docker instance, and stopping that instance wiped all the data and I could start again. I ran this instance with:

docker run -it --name traccar --hostname traccar --rm -v ${PWD}/traccar.xml:/opt/traccar/conf/traccar.xml --publish 8082:8082 --publish 5000-5300:5000-5300 --publish 5000-5300:5000-5300/udp traccar/traccar:latest

When I restarted the instance, I went to http://localhost:8082 and registered: name 'test', email address 'test@test.test', password 'test', then logged in as that user and registered the device '12345'.

Then I could run this nasty, hacky python to try to import the points:

import argparse
import datetime
import json
import requests
import urllib.parse

from time import strftime, localtime

parser = argparse.ArgumentParser(description="Import data into Traccar.")
parser.add_argument(
    "--data-file",
    type=str,
    required=True,
    help="Name of the data file to be imported",
)
args: argparse.Namespace = parser.parse_args()

data_filename: str = args.data_file
device_id: int = 12345
username: str = "test@test.test"
password: str = "test"
url: str = "http://localhost:8082/"

with open(data_filename, mode="r", encoding="utf-8") as data_file:
    data = json.load(data_file)


def geodata_key(props, key):
    if "geodata" in props and "properties" in props["geodata"] and key in props["geodata"]["properties"]:
        return props["geodata"]["properties"][key]
    return None

    
def convert_record(record):
    props = record["properties"]
    return {
        "deviceid": device_id,
        "lat": props["latitude"],
        "lon": props["longitude"],
        "timestamp": datetime.datetime.fromtimestamp(props["timestamp"]).strftime('%Y-%m-%dT%H:%M:%SZ'),
        "speed": props["velocity"] if props["velocity"] else 0,
        "bearing": props["course"],
        "altitude": props["altitude"],
        "accuracy": props["accuracy"],
        "hdop": None,
        "batt": props["battery_status"],
        "imported": datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
        "imported_tracker": props["tracker_id"],
        "imported_country": props["country"],
        "imported_city": props["city"],
        "imported_activity": props["topic"],
        "imported_battery_status": props["battery_status"],
        "imported_ping": props["ping"],
        "imported_battery": props["battery"],
        "imported_trigger": props["trigger"],
        "imported_bssid": props["bssid"],
        "imported_ssid": props["ssid"],
        "imported_connection": props["connection"],
        "imported_vertical_accuracy": props["vertical_accuracy"],
        "imported_mode": props["mode"],
        "imported_course": props["course"],
        "imported_course_accuracy": props["course_accuracy"],
        "imported_external_track_id": props["external_track_id"],
        "imported_track_id": props["track_id"],
        "imported_country_name": props["country_name"],
        "imported_geodata_city": geodata_key(props, "city"),
        "imported_geodata_name": geodata_key(props, "name"),
        "imported_geodata_type": geodata_key(props, "type"),
        "imported_geodata_state": geodata_key(props, "state"),
        "imported_geodata_county": geodata_key(props, "county"),
        "imported_geodata_osm_id": geodata_key(props, "osm_id"),
        "imported_geodata_country": geodata_key(props, "country"),
        "imported_geodata_osm_key": geodata_key(props, "osm_key"),
        "imported_geodata_district": geodata_key(props, "district"),
        "imported_geodata_osm_type": geodata_key(props, "osm_type"),
        "imported_geodata_postcode": geodata_key(props, "postcode"),
        "imported_geodata_osm_value": geodata_key(props, "osm_value"),
        "imported_geodata_countrycode": geodata_key(props, "countrycode"),
    }

    
print(f"Got {len(data["features"])} records.")
cutoff = 10
counter = 0
for record in data["features"]:
    converted = convert_record(record)
    response = requests.post(url, auth=(username, password), data=converted)
    print(response.headers)
    print(response.content)
    print(response.status_code)
    counter += 1
    if counter >= cutoff:
        break
 

That (yep, nasty) code stored the data successfully. Running it on the data in the original message and then fetching via api/position returned the following position:

[
  {
    "id": 1,
    "attributes": {
      "imported": "2026-03-30T14:12:51Z",
      "imported_tracker": "google-maps-timeline-export",
      "imported_country": "New Zealand",
      "imported_activity": "Google Maps Timeline Export",
      "imported_geodata_name": "Proposed Sidle 73 extension from Dixons to Craigieburn Campground",
      "imported_geodata_type": "house",
      "imported_geodata_state": "Canterbury",
      "imported_geodata_county": "Selwyn District",
      "imported_geodata_osm_id": 284390097.0,
      "imported_geodata_country": "New Zealand",
      "imported_geodata_osm_key": "highway",
      "imported_geodata_osm_type": "W",
      "imported_geodata_osm_value": "proposed",
      "imported_geodata_countrycode": "NZ",
      "distance": 0.0,
      "totalDistance": 0.0,
      "motion": false
    },
    "deviceId": 1,
    "protocol": "osmand",
    "serverTime": "2026-03-30T14:12:51.052+00:00",
    "deviceTime": "2015-10-15T00:01:56.000+00:00",
    "fixTime": "2015-10-15T00:01:56.000+00:00",
    "valid": true,
    "latitude": -43.161877,
    "longitude": 171.725274,
    "altitude": 0.0,
    "speed": 0.0,
    "course": 0.0,
    "address": null,
    "accuracy": 0.0,
    "network": null,
    "geofenceIds": null
  }
]

If I were continuing with this I'd obviously make the code better, but I hope it gives someone else a head start with their imports.

Many thanks,

Geoff