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
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
/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: Prefix with '-' for descending order (e.g., Default: |
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
/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
/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
/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/jsonSubjects
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
