Interact with EarthRanger using the API

EarthRanger's API enables direct programmatic access to tracking data, observations, and events, allowing you to build custom integrations, automation pipelines, or external dashboards.

Getting Started

Explore EarthRanger's API through the following official tools and documentation:

Authentication

EarthRanger uses OAuth2 tokens for API access.

  • You can create a long-lived token through your Admin portal
  • Or request one from the EarthRanger Support Team.

Visit Create and Manage Authentication Tokens for Secure Integrations to learn how to Create an Authentication Token in Admin portal.

 

Events

Events represent incidents, activities, or observations in the field such as wildlife sightings, security incidents, or patrol activities.

List Events

GET /api/v1.0/activity/events/

Query Parameters:

Parameter Type Description
page_size Optional integer Number of results per page (default: 25, max: 4000)
state Optional string Filter by state: new, active, resolved
event_type Optional UUID Filter by event type ID
bbox Optional string Bounding box: west,south,east,north
sort_by Optional string

Valid values are event_time, updated_at, created_at, serial_number (prefix with '-' for reverse order)

default is by '-sort_at' which is a special value representing reverse by updated_at.

include_updates Optional boolean Include event update history
include_notes Optional boolean Include event notes
include_files Optional boolean Include attached files

Code Examples:

cURL

Basic request:

curl --location 'https://your-domain.pamdas.org/api/v1.0/activity/events/ \
--header 'Authorization: Bearer YOUR_TOKEN_HERE' \
--header 'Accept: application/json'

With filters:

curl --location 'https://your-domain.pamdas.org/api/v1.0/activity/events/?state=new&page_size=3&sort_by=-updated_at' \
--header 'Authorization: Bearer YOUR_TOKEN_HERE' \
--header 'Accept: application/json'

With date range and bounding box:

curl -X GET "https://your-domain.pamdas.org/api/v1.0/activity/events/?bbox=34.5,-2.5,34.6,-2.3&since=2024-10-01T00:00:00Z&until=2024-10-19T23:59:59Z" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Accept: application/json"
 
 

Python

Using the official EarthRanger Python client (er-client):

Install with: pip install earthranger-client

from erclient.client import ERClient

# Initialize client with token (recommended)
client = ERClient(
    service_root='https://your-domain.pamdas.org/api/v1.0',
    token='YOUR_TOKEN_HERE'
)

# OR initialize with username/password
client = ERClient(
    service_root='https://your-domain.pamdas.org/api/v1.0',
    username='your_username',
    password='your_password',
    token_url='https://your-domain.pamdas.org/oauth2/token',
    client_id='your_client_id'
)

# Basic request - get_events() returns a generator
# It automatically handles pagination and fetches all events
# Note: This may take time if you have many events
for event in client.get_events():
    print(f"Event: {event.get('title', 'No title')} - {event['event_type']}")
    print(f"Location: {event.get('location')}")
    print(f"Time: {event['time']}")
    print("---")
    
# TIP: Use max_results to limit the number of events fetched
for event in client.get_events(max_results=100):
    print(event['title'])

# With filters - supported parameters:
# state, page_size, page, event_type, filter, include_notes,
# include_related_events, include_files, include_details, 
# updated_since, include_updates, max_results, oldest_update_date, event_ids
events_list = []
for event in client.get_events(
    state='active',
    page_size=50,
    include_notes=True,
    include_files=True
):
    events_list.append(event)
    
print(f"Found {len(events_list)} active events")

# Limit results with max_results
count = 0
for event in client.get_events(state='active', max_results=10):
    count += 1
    print(f"{count}. {event['title']}")
    
# Filter by event type
for event in client.get_events(event_type='wildlife_sighting_rep'):
    print(f"Wildlife sighting: {event['title']}")

Using Async client (AsyncERClient) for better performance:

Note: Requires Python 3.7+ with asyncio support

from erclient.client import AsyncERClient
import asyncio  # Built-in Python module

async def list_events():
    # Use as async context manager (recommended)
    async with AsyncERClient(
        service_root='https://your-domain.pamdas.org/api/v1.0',
        token='YOUR_TOKEN_HERE'
    ) as client:
        
        # get_events() returns an async generator
        count = 0
        async for event in client.get_events(
            page_size=100,
            filter='{"state":"active"}'  # JSON string filter
        ):
            print(f"{event['title']} - {event['state']}")
            count += 1
            
        print(f"Total events processed: {count}")

# Run the async function
asyncio.run(list_events())

Using requests library directly (without er-client):

import requests

API_URL = 'https://your-domain.pamdas.org/api/v1.0'
TOKEN = 'YOUR_TOKEN_HERE'

headers = {
    'Authorization': f'Bearer {TOKEN}',
    'Accept': 'application/json'
}

# Make request
params = {
    'state': 'active',
    'page_size': 50,
    'sort_by': '-updated_at'
}

response = requests.get(
    f'{API_URL}/activity/events/',
    headers=headers,
    params=params
)

if response.status_code == 200:
    data = response.json()
    print(f"Total count: {data['count']}")
    
    for event in data['results']:
        print(f"{event['title']} - {event['state']}")
        
    # Handle pagination
    while data.get('next'):
        response = requests.get(data['next'], headers=headers)
        data = response.json()
        for event in data['results']:
            print(f"{event['title']} - {event['state']}")
else:
    print(f"Error: {response.status_code}")
    print(response.text)
 
 

Node.js

Using axios:

const axios = require('axios');

const API_URL = 'https://your-domain.pamdas.org/api/v1.0';
const TOKEN = 'YOUR_TOKEN_HERE';

async function listEvents() {
  try {
    const response = await axios.get(
      `${API_URL}/activity/events/`,
      {
        headers: {
          'Authorization': `Bearer ${TOKEN}`,
          'Accept': 'application/json'
        },
        params: {
          state: 'active',
          page_size: 50,
          sort_by: '-updated_at'
        }
      }
    );
    
    console.log(`Total events: ${response.data.count}`);
    
    response.data.results.forEach(event => {
      console.log(`${event.title || 'Untitled'} - ${event.state}`);
      if (event.location) {
        console.log(`Location: ${event.location.latitude}, ${event.location.longitude}`);
      }
    });
    
    // Check if there are more pages
    if (response.data.next) {
      console.log('More results available at:', response.data.next);
    }
    
    return response.data;
  } catch (error) {
    console.error('Error fetching events:', error.response?.data || error.message);
    throw error;
  }
}

// Run the function
listEvents();

Using native fetch (Node.js 18+):

const API_URL = 'https://your-domain.pamdas.org/api/v1.0';
const TOKEN = 'YOUR_TOKEN_HERE';

async function listEvents() {
  const params = new URLSearchParams({
    state: 'active',
    page_size: '50',
    sort_by: '-updated_at'
  });
  
  const response = await fetch(
    `${API_URL}/activity/events/?${params}`,
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${TOKEN}`,
        'Accept': 'application/json'
      }
    }
  );
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const data = await response.json();
  console.log(`Found ${data.count} events`);
  
  // Process results
  data.results.forEach(event => {
    console.log(`${event.title} - ${event.state}`);
  });
  
  return data;
}

listEvents()
  .then(data => console.log('Success:', data))
  .catch(error => console.error('Error:', error));
 
 

Response Example:

{
  "count": 150,
  "next": "https://your-domain.pamdas.org/api/v1.0/activity/events/?page=2&state=active&page_size=50",
  "previous": null,
  "results": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "serial_number": 12345,
      "event_type": "wildlife_sighting_rep",
      "priority": 200,
      "state": "active",
      "location": {
        "latitude": -2.345678,
        "longitude": 34.567890
      },
      "time": "2024-10-19T14:30:00Z",
      "end_time": null,
      "created_at": "2024-10-19T14:35:00Z",
      "updated_at": "2024-10-19T14:35:00Z",
      "title": "Elephant Sighting",
      "reported_by": {
        "id": "user123",
        "username": "ranger.john",
        "first_name": "John",
        "last_name": "Doe"
      },
      "event_details": {
        "species": "African Elephant",
        "number_of_individuals": 5,
        "behavior": "Feeding"
      }
    },
    {
      "id": "b2c3d4e5-f6g7-8901-bcde-f12345678901",
      "serial_number": 12346,
      "event_type": "security_incident",
      "priority": 500,
      "state": "active",
      "location": {
        "latitude": -2.456789,
        "longitude": 34.678901
      },
      "time": "2024-10-19T13:15:00Z",
      "end_time": null,
      "created_at": "2024-10-19T13:20:00Z",
      "updated_at": "2024-10-19T13:20:00Z",
      "title": "Suspicious Activity Near Boundary",
      "reported_by": {
        "id": "user456",
        "username": "ranger.jane",
        "first_name": "Jane",
        "last_name": "Smith"
      },
      "event_details": {
        "incident_type": "trespassing",
        "number_of_individuals": 3,
        "description": "Three individuals spotted near north fence"
      }
    }
  ]
}

Get Single Event

GET /api/v1.0/activity/event/{id}/

Request:

GET /api/v1.0/activity/event/a1b2c3d4-e5f6-7890-abcd-ef1234567890/
Authorization: Bearer your_token_here

Response (200 OK):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "serial_number": 12345,
  "event_type": "wildlife_sighting_rep",
  "priority": 200,
  "state": "active",
  "location": {
    "latitude": -2.345678,
    "longitude": 34.567890
  },
  "time": "2024-10-19T14:30:00Z",
  "end_time": null,
  "created_at": "2024-10-19T14:35:00Z",
  "updated_at": "2024-10-19T14:35:00Z",
  "title": "Elephant Sighting",
  "reported_by": {
    "id": "user123",
    "username": "ranger.john",
    "first_name": "John",
    "last_name": "Doe"
  },
  "event_details": {
    "species": "African Elephant",
    "number_of_individuals": 5,
    "behavior": "Feeding"
  },
  "notes": [],
  "files": [],
  "event_category": "monitoring"
}

Create an Event

POST /api/v1.0/activity/events/

To create a new Event in EarthRanger, send a POST request with the event details.

Required Fields:

Field Type Description
event_type string Event type value (must match configured event types)
time datetime Event occurrence time (ISO8601 format)
location object Event location with latitude and longitude

Optional Fields:

Field Type Description
priority integer Event priority (0-500, default: 0)
state string Event state: new, active, or resolved
title string Event title/summary
event_details object Additional event-specific data

Request:

POST /api/v1.0/activity/events/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "event_type": "mist_rep",
  "time": "2024-10-19T06:18:44.056439Z",
  "priority": 100,
  "location": {
    "latitude": 47.123,
    "longitude": -122.123
  },
  "title": "Medical Incident - Ranger Station",
  "event_details": {
    "mistrep_Method": "Air Evac",
    "mistrep_Injury": "Malaria",
    "mistrep_Symptoms": "Fever and sweating",
    "mistrep_Treatment": "Anti malarial medicine"
  }
}

Response (201 Created):

{
  "id": "f1e2d3c4-b5a6-7890-cdef-123456789abc",
  "serial_number": 12346,
  "event_type": "mist_rep",
  "priority": 100,
  "state": "new",
  "location": {
    "latitude": 47.123,
    "longitude": -122.123
  },
  "time": "2024-10-19T06:18:44.056439Z",
  "created_at": "2024-10-19T15:20:00Z",
  "updated_at": "2024-10-19T15:20:00Z",
  "title": "Medical Incident - Ranger Station",
  "reported_by": {
    "id": "current_user_id",
    "username": "api.user"
  },
  "event_details": {
    "mistrep_Method": "Air Evac",
    "mistrep_Injury": "Malaria",
    "mistrep_Symptoms": "Fever and sweating",
    "mistrep_Treatment": "Anti malarial medicine"
  }
}

The event_type and each key in event_details must match a field defined in your configured Event Type schema (see Admin > Activity > Event Types).

 

Update an Event

PATCH /api/v1.0/activity/event/{id}/

Request:

PATCH /api/v1.0/activity/event/f1e2d3c4-b5a6-7890-cdef-123456789abc/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "state": "resolved",
  "priority": 50
}

Response (200 OK):

{
  "id": "f1e2d3c4-b5a6-7890-cdef-123456789abc",
  "serial_number": 12346,
  "event_type": "mist_rep",
  "priority": 50,
  "state": "resolved",
  "location": {
    "latitude": 47.123,
    "longitude": -122.123
  },
  "time": "2024-10-19T06:18:44.056439Z",
  "updated_at": "2024-10-19T16:00:00Z",
  "title": "Medical Incident - Ranger Station"
}

List Event Types

GET /api/v2.0/activity/eventtypes/

Query Parameters:

Parameter Type Description
include_inactive Optional boolean Include inactive event types in results

Code Examples:

cURL

curl -X GET "https://your-domain.pamdas.org/api/v2.0/activity/eventtypes/" \\
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \\
  -H "Accept: application/json"
 
 

Python

import requests

API_URL = 'https://your-domain.pamdas.org/api/v2.0'
TOKEN = 'YOUR_TOKEN_HERE'

headers = {
    'Authorization': f'Bearer {TOKEN}',
    'Accept': 'application/json'
}

response = requests.get(f'{API_URL}/activity/eventtypes/', headers=headers)

if response.status_code == 200:
    data = response.json()
    for event_type in data['data']:
        print(f"{event_type['display']} ({event_type['value']})")
        print(f"  Category: {event_type['category']}")
        print(f"  Default Priority: {event_type['default_priority']}")
else:
    print(f"Error: {response.status_code}")
    print(response.text)
 
 

Node.js

const axios = require('axios');

const API_URL = 'https://your-domain.pamdas.org/api/v2.0';
const TOKEN = 'YOUR_TOKEN_HERE';

async function listEventTypes() {
  try {
    const response = await axios.get(
      \`\${API_URL}/activity/eventtypes/\`,
      {
        headers: {
          'Authorization': \`Bearer \${TOKEN}\`,
          'Accept': 'application/json'
        }
      }
    );
    
    console.log('Event Types:');
    response.data.data.forEach(eventType => {
      console.log(\`\${eventType.display} (\${eventType.value})\`);
      console.log(\`  Category: \${eventType.category}\`);
    });
    
    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}

listEventTypes();
 
 

Response Example:

{
  "data": [
    {
      "id": "8e2812d8-4ac9-4874-b1b2-1adc5ea192b5",
      "url": "https://your-domain.pamdas.org/api/v2.0/activity/eventtypes/snare_rep/",
      "has_events_assigned": true,
      "icon_id": "snare_rep",
      "value": "snare_rep",
      "display": "Snare",
      "ordernum": 160,
      "category": "security",
      "geometry_type": "Polygon",
      "default_priority": 0,
      "default_state": "new",
      "resolve_time": null,
      "auto_resolve": false,
      "is_collection": false,
      "is_active": true,
      "icon": "snare_rep"
    }
  ]
}

Get Single Event Type

GET /api/v2.0/activity/eventtypes/{eventtype_value}/

Query Parameters:

Parameter Type Description
include_inactive Optional boolean Include inactive event types
include_schema Optional boolean Include full JSON schema definition

Request:

GET /api/v2.0/activity/eventtypes/snare_rep/?include_schema=true
Authorization: Bearer your_token_here

Response (200 OK):

{
  "data": {
    "id": "8e2812d8-4ac9-4874-b1b2-1adc5ea192b5",
    "value": "snare_rep",
    "display": "Snare",
    "category": "security",
    "default_priority": 0,
    "default_state": "new",
    "schema": {
      "json": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "number_of_snares_found": {
            "type": "number",
            "title": "Number of Snares Found"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "comments": {
            "type": "string",
            "title": "Comments",
            "default": ""
          }
        }
      },
      "ui": {
        "fields": {
          "number_of_snares_found": {
            "type": "NUMERIC",
            "parent": "section-1"
          },
          "status": {
            "type": "CHOICE_LIST",
            "inputType": "DROPDOWN",
            "parent": "section-1"
          }
        }
      }
    }
  }
}

Create Event Type

POST /api/v2.0/activity/eventtypes/

Create a new event type with JSON Schema validation and UI configuration.

Required Fields:

Field Type Description
value string Unique identifier for the event type
display string Display name
schema string JSON Schema definition (stringified JSON)

Optional Fields:

Field Type Description
category string Event category (e.g., security, monitoring, logistics)
default_priority integer Default priority (0-500)
default_state string Default state (new, active, resolved)
geometry_type string Point, LineString, or Polygon
icon string Icon identifier
auto_resolve boolean Auto-resolve after resolve_time
resolve_time integer Auto-resolve time in seconds
is_active boolean Whether the event type is active

Request:

POST /api/v2.0/activity/eventtypes/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "value": "custom_incident",
  "display": "Custom Incident",
  "category": "security",
  "default_priority": 300,
  "default_state": "new",
  "geometry_type": "Point",
  "is_active": true,
  "schema": "{\\"json\\":{\\"$schema\\":\\"https://json-schema.org/draft/2020-12/schema\\",\\"type\\":\\"object\\",\\"properties\\":{\\"description\\":{\\"type\\":\\"string\\",\\"title\\":\\"Description\\"}}}}"
}

Response (201 Created):

{
  "data": {
    "resource_url": "/api/v2.0/activity/eventtypes/custom_incident/"
  }
}

Update Event Type

PATCH /api/v2.0/activity/eventtypes/{eventtype_value}/

Request:

PATCH /api/v2.0/activity/eventtypes/custom_incident/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "default_priority": 400,
  "is_active": false
}

Response (200 OK):

{
  "data": {
    "id": "uuid-here",
    "value": "custom_incident",
    "display": "Custom Incident",
    "default_priority": 400,
    "is_active": false
  }
}

Delete Event Type

DELETE /api/v2.0/activity/eventtypes/{eventtype_value}/

Request:

DELETE /api/v2.0/activity/eventtypes/custom_incident/
Authorization: Bearer your_token_here

Response Codes:

  • 204 No Content: Successfully deleted
  • 404 Not Found: Event type not found
  • 409 Conflict: Event type has associated events and cannot be deleted

Note: Event types with associated events cannot be deleted. You must first remove or reassign all events before deletion.

 

Posting sensor data

The general concept is that a source provider is a service that describes one or more sources that provide location information for a subject.

For example, a tracking device manufacturer is a source provider, a rhino tracker is a source and the rhino itself the subject.

As is typical, API headers:

Authorization: Bearer <token>
Accept: application/json
Content-Disposition: attachment; filename={}
Content-Type: application/json

Subjects

Subjects represent tracked entities such as wildlife, rangers, vehicles, or any monitored asset.

List Subjects

GET /api/v1.0/subjects/

Query Parameters:

Parameter Type Description
subject_group Optional UUID Filter by subject group
tracks Optional boolean Include recent track data
tracks_since Optional datetime Start time for tracks (ISO8601)
bbox Optional string Filter by bounding box
subject_type Optional string Filter by subject type

Request:

GET /api/v1.0/subjects/?tracks=true&tracks_since=2024-10-01T00:00:00Z
Authorization: Bearer your_token_here

Response (200 OK):

[
  {
    "id": "abc12345-def6-7890-ghij-klmnopqrstuv",
    "name": "Elephant_001",
    "subject_type": "wildlife",
    "subject_subtype": "elephant",
    "is_active": true,
    "region": "North Sector",
    "sex": "male",
    "additional": {
      "age_years": 15,
      "collar_id": "COL-001"
    },
    "last_position": {
      "id": "pos123",
      "location": {
        "latitude": -2.345678,
        "longitude": 34.567890
      },
      "recorded_at": "2024-10-19T14:00:00Z"
    },
    "tracks": [
      {
        "location": {
          "latitude": -2.345678,
          "longitude": 34.567890
        },
        "recorded_at": "2024-10-19T14:00:00Z"
      },
      {
        "location": {
          "latitude": -2.345123,
          "longitude": 34.568123
        },
        "recorded_at": "2024-10-19T13:00:00Z"
      }
    ]
  }
]

Get Single Subject

GET /api/v1.0/subject/{id}/

Request:

GET /api/v1.0/subject/abc12345-def6-7890-ghij-klmnopqrstuv/
Authorization: Bearer your_token_here

Response (200 OK):

{
  "id": "abc12345-def6-7890-ghij-klmnopqrstuv",
  "name": "Elephant_001",
  "subject_type": "wildlife",
  "subject_subtype": "elephant",
  "is_active": true,
  "region": "North Sector",
  "sex": "male",
  "additional": {
    "age_years": 15,
    "collar_id": "COL-001"
  },
  "last_position": {
    "id": "pos123",
    "location": {
      "latitude": -2.345678,
      "longitude": 34.567890
    },
    "recorded_at": "2024-10-19T14:00:00Z"
  }
}

Create a Subject

POST /api/v1.0/subjects/

Create a new subject in EarthRanger. Subjects are typically created automatically when observations are posted, but can also be created manually.

Required Fields:

Field Type Description
name string Subject name (must be unique)
subject_subtype UUID or string Subject subtype identifier

Request:

POST /api/v1.0/subjects/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "name": "Ranger_Vehicle_05",
  "subject_subtype": "4x4",
  "additional": {
    "plate_number": "GNP-005",
    "model": "Land Cruiser"
  }
}

Response (201 Created):

{
  "id": "xyz98765-abc4-3210-defg-hijklmnopqrs",
  "name": "Ranger_Vehicle_05",
  "subject_type": "vehicle",
  "subject_subtype": "4x4",
  "is_active": true,
  "additional": {
    "plate_number": "GNP-005",
    "model": "Land Cruiser"
  },
  "created_at": "2024-10-19T15:30:00Z"
}

Observations

Observations are individual location/tracking data points collected from devices or sources.

List Observations

GET /api/v1.0/observations/

Query Parameters:

Parameter Type Description
subject_id Optional UUID Filter by subject
source_id Optional UUID Filter by source
since Optional datetime Start date/time (ISO8601)
until Optional datetime End date/time (ISO8601)

Request:

GET /api/v1.0/observations/?subject_id=abc123&since=2024-10-01T00:00:00Z
Authorization: Bearer your_token_here

Response (200 OK):

[
  {
    "id": "obs-12345",
    "location": {
      "latitude": -2.345678,
      "longitude": 34.567890
    },
    "recorded_at": "2024-10-19T14:00:00Z",
    "created_at": "2024-10-19T14:05:00Z",
    "source": "gps-collar-001",
    "subject": {
      "id": "abc123",
      "name": "Elephant_001"
    },
    "additional": {
      "speed": 2.5,
      "heading": 145
    }
  }
]

Create an Observation

POST /api/v1.0/observations/

Post a track point or sensor reading. If the system hasn't seen the source-provider/manufacturer_id combination before, it will create sources and subjects automatically.

Required Fields:

Field Type Description
location object Location object with lat and lon
recorded_at datetime Time the observation was recorded (ISO8601)

Optional Fields:

Field Type Description
manufacturer_id string Unique device identifier
source_type string Type of source (e.g., tracking_device)
subject_name string Name of the subject being tracked
subject_type string Type of subject (e.g., wildlife, person, vehicle)
subject_subtype string Subtype of subject (e.g., elephant, ranger, car)
additional object Additional metadata (speed, heading, etc.)

Request:

POST /api/v1.0/observations/
Authorization: Bearer your_token_here
Content-Type: application/json

{
  "location": {
    "lat": 47.123,
    "lon": -122.123
  },
  "recorded_at": "2024-10-19T13:59:15.000Z",
  "manufacturer_id": "COLLAR-12345",
  "subject_name": "Elephant_001",
  "subject_type": "wildlife",
  "subject_subtype": "elephant",
  "source_type": "tracking_device",
  "additional": {
    "speed": 2.5,
    "heading": 145,
    "battery_level": 85
  }
}

Response (201 Created):

{
  "id": "obs-98765",
  "location": {
    "latitude": 47.123,
    "longitude": -122.123
  },
  "recorded_at": "2024-10-19T13:59:15.000Z",
  "created_at": "2024-10-19T14:00:00Z",
  "source": {
    "id": "src-12345",
    "manufacturer_id": "COLLAR-12345",
    "source_type": "tracking_device"
  },
  "subject": {
    "id": "subj-67890",
    "name": "Elephant_001",
    "subject_type": "wildlife",
    "subject_subtype": "elephant"
  },
  "additional": {
    "speed": 2.5,
    "heading": 145,
    "battery_level": 85
  }
}

Note: If you pass in an observation where the system hasn't seen that source-provider/manufacturer_id combination before, it'll create sources and subjects as necessary.

 

Subject Groups

Subject groups organize subjects into hierarchical categories for management and filtering.

List Subject Groups

GET /api/v1.0/subjectgroups/

Request:

GET /api/v1.0/subjectgroups/
Authorization: Bearer your_token_here

Response (200 OK):

[
  {
    "id": "grp-12345",
    "name": "Wildlife",
    "subject_count": 45,
    "is_visible": true,
    "children": [
      {
        "id": "grp-12346",
        "name": "Elephants",
        "subject_count": 12,
        "is_visible": true
      },
      {
        "id": "grp-12347",
        "name": "Rhinos",
        "subject_count": 8,
        "is_visible": true
      }
    ]
  },
  {
    "id": "grp-12348",
    "name": "Rangers",
    "subject_count": 25,
    "is_visible": true
  }
]

List Subjects in Group

GET /api/v1.0/subjectgroup/{id}/subjects/

Request:

GET /api/v1.0/subjectgroup/grp-12346/subjects/
Authorization: Bearer your_token_here

Response (200 OK):

[
  {
    "id": "subj-001",
    "name": "Elephant_001",
    "subject_type": "wildlife",
    "subject_subtype": "elephant",
    "is_active": true,
    "last_position": {
      "latitude": -2.345678,
      "longitude": 34.567890,
      "recorded_at": "2024-10-19T14:00:00Z"
    }
  },
  {
    "id": "subj-002",
    "name": "Elephant_002",
    "subject_type": "wildlife",
    "subject_subtype": "elephant",
    "is_active": true,
    "last_position": {
      "latitude": -2.346789,
      "longitude": 34.568901,
      "recorded_at": "2024-10-19T14:05:00Z"
    }
  }
]

Patrols

Patrols represent field operations with segments, waypoints, and associated events.

List Patrols

GET /api/v1.0/activity/patrols/

Query Parameters:

Parameter Type Description
exclude_empty_patrols Optional boolean Exclude patrols with no segments or events
patrol_type Optional UUID Filter by patrol type
since Optional datetime Start date/time (ISO8601)
until Optional datetime End date/time (ISO8601)

Request:

GET /api/v1.0/activity/patrols/?exclude_empty_patrols=true
Authorization: Bearer your_token_here

Response (200 OK):

{
  "count": 10,
  "results": [
    {
      "id": "patrol-12345",
      "serial_number": 501,
      "title": "Morning Patrol - North Sector",
      "patrol_type": "routine",
      "state": "done",
      "start_time": "2024-10-19T06:00:00Z",
      "end_time": "2024-10-19T12:00:00Z",
      "patrol_segments": [
        {
          "id": "seg-001",
          "scheduled_start": "2024-10-19T06:00:00Z",
          "scheduled_end": "2024-10-19T08:00:00Z",
          "time_range": {
            "start_time": "2024-10-19T06:15:00Z",
            "end_time": "2024-10-19T07:45:00Z"
          },
          "leader": {
            "id": "user-123",
            "name": "John Ranger"
          }
        }
      ],
      "events": [
        {
          "id": "evt-456",
          "event_type": "wildlife_sighting_rep",
          "time": "2024-10-19T07:30:00Z"
        }
      ]
    }
  ]
}

Get Single Patrol

GET /api/v1.0/activity/patrol/{id}/

Request:

GET /api/v1.0/activity/patrol/patrol-12345/
Authorization: Bearer your_token_here

Response (200 OK):

{
  "id": "patrol-12345",
  "serial_number": 501,
  "title": "Morning Patrol - North Sector",
  "patrol_type": "routine",
  "state": "done",
  "start_time": "2024-10-19T06:00:00Z",
  "end_time": "2024-10-19T12:00:00Z",
  "created_at": "2024-10-18T20:00:00Z",
  "updated_at": "2024-10-19T12:05:00Z",
  "patrol_segments": [
    {
      "id": "seg-001",
      "scheduled_start": "2024-10-19T06:00:00Z",
      "scheduled_end": "2024-10-19T08:00:00Z",
      "time_range": {
        "start_time": "2024-10-19T06:15:00Z",
        "end_time": "2024-10-19T07:45:00Z"
      },
      "leader": {
        "id": "user-123",
        "username": "john.ranger",
        "name": "John Ranger"
      },
      "events": [
        {
          "id": "evt-456",
          "event_type": "wildlife_sighting_rep",
          "title": "Elephant Sighting",
          "time": "2024-10-19T07:30:00Z"
        }
      ]
    }
  ],
  "notes": []
}

Subject Tracks

Track data shows movement history for a subject over time.

Get Subject Tracks

GET /api/v1.0/subject/{subject_id}/tracks/

Query Parameters:

Parameter Type Description
since Optional datetime Start date/time (ISO8601)
until Optional datetime End date/time (ISO8601)
format Optional string Response format: json, geojson, or csv

Request:

GET /api/v1.0/subject/abc123/tracks/?since=2024-10-01T00:00:00Z
Authorization: Bearer your_token_here

Response (200 OK):

[
  {
    "id": "track-001",
    "location": {
      "latitude": -2.345678,
      "longitude": 34.567890
    },
    "recorded_at": "2024-10-19T14:00:00Z",
    "created_at": "2024-10-19T14:05:00Z",
    "source": "gps-collar-001",
    "additional": {
      "speed": 2.5,
      "heading": 145,
      "altitude": 1250
    }
  },
  {
    "id": "track-002",
    "location": {
      "latitude": -2.345123,
      "longitude": 34.568123
    },
    "recorded_at": "2024-10-19T13:00:00Z",
    "created_at": "2024-10-19T13:05:00Z",
    "source": "gps-collar-001",
    "additional": {
      "speed": 1.8,
      "heading": 150,
      "altitude": 1245
    }
  }
]

Common Patterns

Date/Time Format

All datetime parameters must be in ISO8601 format with timezone:

2024-10-19T14:30:00Z          # UTC
2024-10-19T14:30:00-06:00     # With timezone offset

Pagination

Most list endpoints support pagination with page and page_size parameters. The response includes count, next, and previous fields.

Request Page 1:

GET /api/v1.0/activity/events/?page_size=25

Response:

{
  "count": 150,
  "next": "https://your-domain.pamdas.org/api/v1.0/activity/events/?page=2&page_size=25",
  "previous": null,
  "results": [...]
}

Request Next Page:

GET /api/v1.0/activity/events/?page=2&page_size=25

Filtering by Bounding Box

Format: west,south,east,north (longitude_min, latitude_min, longitude_max, latitude_max)

?bbox=-122.5,48.4,-122.0,49.0

Get all events within a geographic area:

GET /api/v1.0/activity/events/?bbox=34.5,-2.5,34.6,-2.3
Authorization: Bearer your_token_here

This returns all events with locations between:

  • Longitude: 34.5° to 34.6° East
  • Latitude: -2.5° to -2.3° South

Error Handling

The API returns standard HTTP status codes. Common error responses:

Status Code Meaning Common Causes
400 Bad Request Invalid parameters or malformed JSON
401 Unauthorized Missing or invalid authentication token
403 Forbidden Insufficient permissions
404 Not Found Resource doesn't exist
500 Server Error Internal server error

400 Bad Request:

{
  "status": {
    "code": 400,
    "message": "Bad Request",
    "details": "Invalid datetime format for 'since' parameter"
  }
}

401 Unauthorized:

{
  "detail": "Authentication credentials were not provided."
}

 


Up Next: Create and Manage Authentication Tokens for Secure Integrations