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:
-
Interactive Documentation
- Replace
<sandbox>
in the URL with your server name to interact with the API directly: https://sandbox.pamdas.org/api/v1.0/docs/interactive/
- Replace
-
Python Library.
- Dive into our Python library available on GitHub. Find it here: https://github.com/PADAS/er-client
-
Full REST API Documentation
- Once logged in to the EarthRanger admin portal, you can access the full REST API, documented here: https://sandbox.pamdas.org/api/v1.0/docs/index.html
-
Examples
- Explore this article and /docs/examples
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
/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 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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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
/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