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

All API requests require a Bearer token for authentication. Include your token in the Authorization header of every request.

How to Get a Token

You can obtain an authentication token in two ways:

  • Admin Portal: Create a long-lived token through your EarthRanger Admin portal (recommended for integrations)
  • Support Team: Request a token from the EarthRanger Support Team

Using Your Token

Include your Bearer token in the Authorization header of all API requests:

curl https://your-domain.pamdas.org/api/v1.0/subjects \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Accept: application/json"

Security Note: Keep your token secure and never share it publicly. Treat it like a password.

 

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/

Retrieve a list of events with optional filtering, sorting, and pagination.

Query Parameters

Parameter Type Description
page Optional integer Page number for pagination (default: 1)
page_size Optional integer Number of results per page (default: 25, max: 4000)
state Optional string Filter by state: new, active, resolved. Can specify multiple values.
event_type Optional string Filter by event type value (e.g., wildlife_sighting_rep). Can specify multiple values.
event_ids Optional string Filter by specific event IDs (comma-separated UUIDs)
event_category Optional string Filter by event category (e.g., security, monitoring)
bbox Optional string Bounding box filter: west,south,east,north (lon_min,lat_min,lon_max,lat_max)
since Optional datetime Start date/time for event_time (ISO8601 format)
until Optional datetime End date/time for event_time (ISO8601 format)
updated_since Optional datetime Filter events updated after this time (ISO8601 format)
sort_by Optional string

Valid values: event_time, updated_at, created_at, serial_number

Prefix with '-' for descending order (e.g., -updated_at)

Default: -sort_at (descending by updated_at)

filter Optional JSON string Advanced filter object for complex queries (e.g., {"text": "elephant"})
is_collection Optional boolean Filter for collection events (true) or individual events (false)
exclude_contained Optional boolean Exclude events contained within collection events (default: false)
patrol_segment Optional UUID Filter events associated with a specific patrol segment
include_updates Optional boolean Include event update history (default: false)
include_notes Optional boolean Include event notes (default: false)
include_files Optional boolean Include attached files (default: false)
include_related_events Optional boolean Include related events (default: false)
include_details Optional boolean Include full event details (default: true)

Code Examples

cURL

Basic request:

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

With filters and pagination:

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

With date range and bounding box:

curl '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'

With advanced filter (text search):

# URL-encoded filter: {"text":"elephant"}
curl 'https://your-domain.pamdas.org/api/v1.0/activity/events/?filter=%7B%22text%22%3A%22elephant%22%7D' \
  -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
client = ERClient(
    service_root='https://your-domain.pamdas.org/api/v1.0',
    token='YOUR_TOKEN_HERE'
)

# Basic request - auto-paginates through all results
for event in client.get_events():
    print(f"{event['serial_number']}: {event.get('title', 'No title')}")
    print(f"  Type: {event['event_type']}")
    print(f"  Location: {event.get('location')}")
    print(f"  Time: {event['time']}")

# With filters and limits
events_list = []
for event in client.get_events(
    state='active',
    page_size=50,
    include_notes=True,
    include_files=True,
    max_results=100  # Limit total results
):
    events_list.append(event)

print(f"Found {len(events_list)} active events")

# Filter by date range
from datetime import datetime, timedelta
since = datetime.now() - timedelta(days=7)
for event in client.get_events(
    state='active',
    since=since.isoformat(),
    max_results=50
):
    print(f"Recent event: {event['title']}")

Using requests library directly:

import requests

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

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

# Request with multiple filters
params = {
    'state': ['active', 'new'],  # Multiple values
    'page_size': 50,
    'sort_by': '-updated_at',
    'include_notes': 'true',
    'since': '2024-10-01T00:00:00Z'
}

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

if response.status_code == 200:
    data = response.json()
    print(f"Total: {data['count']} events")
    print(f"This page: {len(data['results'])} results")

    for event in data['results']:
        print(f"{event['serial_number']}: {event['title']} - {event['state']}")

    # Manual pagination
    while data.get('next'):
        print(f"Fetching next page...")
        response = requests.get(data['next'], headers=headers)
        data = response.json()
        for event in data['results']:
            print(f"{event['serial_number']}: {event['title']}")
else:
    print(f"Error {response.status_code}: {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', 'new'],  // Multiple values
          page_size: 50,
          sort_by: '-updated_at',
          include_notes: true,
          since: '2024-10-01T00:00:00Z'
        }
      }
    );

    console.log(`Total: ${response.data.count} events`);
    console.log(`This page: ${response.data.results.length} results`);

    response.data.results.forEach(event => {
      console.log(`${event.serial_number}: ${event.title || 'Untitled'} - ${event.state}`);
      if (event.location) {
        console.log(`  Location: ${event.location.latitude}, ${event.location.longitude}`);
      }
    });

    // Check for more pages
    if (response.data.next) {
      console.log('More results available');
    }

    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}

listEvents();
 
 

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"
      },
      "event_category": {
        "id": "cat-123",
        "value": "monitoring",
        "display": "Monitoring"
      },
      "patrol_segments": []
    }
  ]
}

Get Single Event

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

Retrieve detailed information about a specific event by its ID.

Code Examples

cURL

curl 'https://your-domain.pamdas.org/api/v1.0/activity/event/a1b2c3d4-e5f6-7890-abcd-ef1234567890/' \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE' \
  -H 'Accept: application/json'
 
 

Python

import requests

API_URL = 'https://your-domain.pamdas.org/api/v1.0'
TOKEN = 'YOUR_TOKEN_HERE'
EVENT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'

headers = {'Authorization': f'Bearer {TOKEN}'}

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

if response.status_code == 200:
    event = response.json()
    print(f"Event: {event['title']}")
    print(f"Type: {event['event_type']}")
    print(f"State: {event['state']}")
    print(f"Details: {event['event_details']}")
else:
    print(f"Error {response.status_code}: {response.text}")
 
 

Node.js

const axios = require('axios');

const API_URL = 'https://your-domain.pamdas.org/api/v1.0';
const TOKEN = 'YOUR_TOKEN_HERE';
const EVENT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

async function getEvent(eventId) {
  try {
    const response = await axios.get(
      `${API_URL}/activity/event/${eventId}/`,
      {
        headers: {
          'Authorization': `Bearer ${TOKEN}`,
          'Accept': 'application/json'
        }
      }
    );

    console.log('Event:', response.data.title);
    console.log('Type:', response.data.event_type);
    console.log('State:', response.data.state);

    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}

getEvent(EVENT_ID);
 
 

Response Example

{
  "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",
    "age_group": "adult"
  },
  "notes": [],
  "files": [],
  "event_category": {
    "id": "cat-123",
    "value": "monitoring",
    "display": "Monitoring"
  },
  "patrol_segments": []
}

Create an Event

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

Create a new event (incident, observation, or report).

Required Fields

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

Optional Fields

Field Type Description
priority Optional integer Event priority (0-500, default: 0)
state Optional string Event state: new, active, or resolved (default: new)
title Optional string Event title/summary
event_details Optional object Additional event-specific data (must match event type schema)
end_time Optional datetime End time for events with duration
is_collection Optional boolean Whether this is a collection event (default: false)
icon_id Optional string Icon identifier
reported_by Optional UUID Subject ID of who reported the event
patrol_segments Optional array Array of patrol segment UUIDs

Code Examples

cURL

curl -X POST 'https://your-domain.pamdas.org/api/v1.0/activity/events/' \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
    "event_type": "wildlife_sighting_rep",
    "time": "2024-10-19T14:30:00Z",
    "priority": 200,
    "location": {
      "latitude": -2.345678,
      "longitude": 34.567890
    },
    "title": "Elephant Herd Sighting",
    "event_details": {
      "species": "African Elephant",
      "number_of_individuals": 8,
      "behavior": "Drinking at waterhole",
      "age_group": "mixed"
    }
  }'
 
 

Python

import requests
from datetime import datetime

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

headers = {
    'Authorization': f'Bearer {TOKEN}',
    'Content-Type': 'application/json'
}

new_event = {
    'event_type': 'wildlife_sighting_rep',
    'time': datetime.utcnow().isoformat() + 'Z',
    'priority': 200,
    'location': {
        'latitude': -2.345678,
        'longitude': 34.567890
    },
    'title': 'Elephant Herd Sighting',
    'event_details': {
        'species': 'African Elephant',
        'number_of_individuals': 8,
        'behavior': 'Drinking at waterhole',
        'age_group': 'mixed'
    }
}

response = requests.post(
    f'{API_URL}/activity/events/',
    headers=headers,
    json=new_event
)

if response.status_code == 201:
    event = response.json()
    print(f"Created event ID: {event['id']}")
    print(f"Serial number: {event['serial_number']}")
else:
    print(f"Error {response.status_code}: {response.text}")
 
 

Node.js

const axios = require('axios');

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

async function createEvent() {
  const newEvent = {
    event_type: 'wildlife_sighting_rep',
    time: new Date().toISOString(),
    priority: 200,
    location: {
      latitude: -2.345678,
      longitude: 34.567890
    },
    title: 'Elephant Herd Sighting',
    event_details: {
      species: 'African Elephant',
      number_of_individuals: 8,
      behavior: 'Drinking at waterhole',
      age_group: 'mixed'
    }
  };

  try {
    const response = await axios.post(
      `${API_URL}/activity/events/`,
      newEvent,
      {
        headers: {
          'Authorization': `Bearer ${TOKEN}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('Created event ID:', response.data.id);
    console.log('Serial number:', response.data.serial_number);

    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}

createEvent();
 
 

Response Example (201 Created)

{
  "id": "f1e2d3c4-b5a6-7890-cdef-123456789abc",
  "serial_number": 12346,
  "event_type": "wildlife_sighting_rep",
  "priority": 200,
  "state": "new",
  "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 Herd Sighting",
  "reported_by": null,
  "event_details": {
    "species": "African Elephant",
    "number_of_individuals": 8,
    "behavior": "Drinking at waterhole",
    "age_group": "mixed"
  },
  "event_category": {
    "id": "cat-123",
    "value": "monitoring",
    "display": "Monitoring"
  },
  "patrol_segments": []
}

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