Using the API

Using our People Groups API


The PeopleGroups.org API provides free, public access to research data on more than 12,000 people groups worldwide. Use it to build maps, prayer tools, research applications, and more — no account or API key required. What will you build next?!

Base URL: https://peoplegroups.org/wp-json/pg/v1

All endpoints are read-only and publicly accessible. No authentication is required.

Endpoints

List People Groups

GET /people-groups

Returns a paginated list of all people group records.

Query Parameters

Parameter Type Default Description
page integer 1 Page number (1-based).
per_page integer 100 Results per page. Maximum 250.

Response Headers

Header Description
X-WP-Total Total number of people group records in the dataset.
X-WP-TotalPages Total number of pages at the requested per_page size.

Example Request

GET https://peoplegroups.org/wp-json/pg/v1/people-groups?page=1&per_page=100

Example Response

[
  {
    "PGID": "PG012345",
    "NmDisp": "Nateni",
    "Ctry": "Benin",
    "Pop": 131000,
    "Rlgn": "Ethnoreligion - Animism",
    "Lang": "Nateni",
    "LPI": 1,
    "LPIname": "Pioneer Unreached People Group",
    "SPI": 1,
    "SPIdesc": "Engaged yet Unreached",
    "GSEC": 1,
    "GSECbrf": "Less than 2% Evangelical, No Active CP Activity",
    "Latitude": 10.52,
    "Longitude": 1.22,
    ...
  },
  ...
]

Get a Single People Group

GET /people-groups/{pgid}

Returns a single people group record by PGID.

Path Parameters

Parameter Type Description
pgid string People Group ID — e.g. PG012345.

Example Request

GET https://peoplegroups.org/wp-json/pg/v1/people-groups/PG012345

Example Response

{
  "OBJECTID": 10508,
  "PEID": 12345,
  "PGID": "PG012345",
  "Name": "Nateni",
  "NmDisp": "Nateni",
  "NmAlt": null,
  "ISOalpha3": "BEN",
  "Ctry": "Benin",
  "Regn": "Africa",
  "RegnSub": "Western Africa",
  "Pop": 131000,
  "Rlgn": "Ethnoreligion - Animism",
  "Lang": "Nateni",
  "LangFamily": "Atlantic-Congo",
  "ROL": "ntm",
  "LPI": 1,
  "LPIname": "Pioneer Unreached People Group",
  "LPIdesc": "0.1% to 0.5% Evangelical",
  "SPI": 1,
  "SPIdesc": "Engaged yet Unreached",
  "GSEC": 1,
  "GSECbrf": "Less than 2% Evangelical, No Active CP Activity",
  "GSEClng": "this people group is less than 2% evangelical, some evangelical resources are available, but there has been no active church planting among them within the past two years",
  "EvngLvl": "Less than 2%",
  "CongExst": "Yes",
  "Plnting": "No Churches Planted",
  "EngStat": "Engaged",
  "Bible": "Available",
  "Jesus": "Not Available",
  "ResTot": 3,
  "PeopleDesc": "an indigenous community of Benin; a dialect subgroup of Nateni (ntm)",
  "PicURL": "https://joshuaproject.net/assets/media/profiles/photos/p13251.jpg",
  "PicCrdt": "Photo courtesy of Joshua Project. Photo Source: Matt & Sarah Murdock",
  "Photo": "Y",
  "Latitude": 10.52,
  "Longitude": 1.22,
  "UpdatedDate": "2026-03-27T04:24:27.000+00:00"
}

Error Response — 404 Not Found

{
  "code": "pg_not_found",
  "message": "No people group found for PGID: PG999999",
  "data": { "status": 404 }
}

Code Examples – Simple

Requires the requests library (pip install requests).

Fetch a single people group

import requests

response = requests.get(
    "https://peoplegroups.org/wp-json/pg/v1/people-groups/PG012345"
)
response.raise_for_status()

group = response.json()
print(group["NmDisp"])   # Nateni
print(group["Ctry"])     # Benin
print(group["LPIname"])  # Pioneer Unreached People Group

Page through all people groups

import requests

BASE_URL = "https://peoplegroups.org/wp-json/pg/v1/people-groups"
page = 1
all_groups = []

while True:
    response = requests.get(BASE_URL, params={"page": page, "per_page": 250})
    response.raise_for_status()

    batch = response.json()
    all_groups.extend(batch)

    total_pages = int(response.headers.get("X-WP-TotalPages", 1))
    if page >= total_pages:
        break
    page += 1

print(f"Fetched {len(all_groups)} people groups")

Uses the native fetch API (Node 18+). No dependencies required.

Fetch a single people group

const response = await fetch(
  "https://peoplegroups.org/wp-json/pg/v1/people-groups/PG012345"
);

if (!response.ok) {
  const error = await response.json();
  throw new Error(error.message);
}

const group = await response.json();
console.log(group.NmDisp);   // Nateni
console.log(group.Ctry);     // Benin
console.log(group.LPIname);  // Pioneer Unreached People Group

Page through all people groups

const BASE_URL = "https://peoplegroups.org/wp-json/pg/v1/people-groups";
let page = 1;
let totalPages = 1;
const allGroups = [];

do {
  const response = await fetch(`${BASE_URL}?page=${page}&per_page=250`);
  const batch = await response.json();
  allGroups.push(...batch);

  totalPages = parseInt(response.headers.get("X-WP-TotalPages") ?? "1", 10);
  page++;
} while (page <= totalPages);

console.log(`Fetched ${allGroups.length} people groups`);

Fetch a single people group (WordPress context)

$response = wp_remote_get(
    'https://peoplegroups.org/wp-json/pg/v1/people-groups/PG012345'
);

if ( is_wp_error( $response ) ) {
    error_log( $response->get_error_message() );
    return;
}

$group = json_decode( wp_remote_retrieve_body( $response ), true );
echo esc_html( $group['NmDisp'] );  // Nateni
echo esc_html( $group['Ctry'] );    // Benin

Fetch a single people group (plain PHP)

$pgid = 'PG012345';
$url  = 'https://peoplegroups.org/wp-json/pg/v1/people-groups/' . urlencode( $pgid );
$json = file_get_contents( $url );

if ( $json === false ) {
    throw new RuntimeException( 'Failed to fetch people group data' );
}

$group = json_decode( $json, true );
echo htmlspecialchars( $group['NmDisp'] );  // Nateni

Page through all people groups

$base_url    = 'https://peoplegroups.org/wp-json/pg/v1/people-groups';
$page        = 1;
$total_pages = 1;
$all_groups  = [];

do {
    $url      = $base_url . '?' . http_build_query( [ 'page' => $page, 'per_page' => 250 ] );
    $response = wp_remote_get( $url );

    if ( is_wp_error( $response ) ) {
        break;
    }

    $batch       = json_decode( wp_remote_retrieve_body( $response ), true );
    $all_groups  = array_merge( $all_groups, $batch );
    $total_pages = (int) wp_remote_retrieve_header( $response, 'x-wp-totalpages' );
    $page++;
} while ( $page <= $total_pages );

echo count( $all_groups ) . ' people groups fetched';

Requires reqwest and serde_json. Add to Cargo.toml:

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }

Fetch a single people group

use reqwest;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://peoplegroups.org/wp-json/pg/v1/people-groups/PG012345";
    let group: Value = reqwest::get(url).await?.json().await?;

    println!("{}", group["NmDisp"]);   // "Nateni"
    println!("{}", group["Ctry"]);     // "Benin"
    println!("{}", group["LPIname"]); // "Pioneer Unreached People Group"

    Ok(())
}

Page through all people groups

use reqwest::Client;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let base_url = "https://peoplegroups.org/wp-json/pg/v1/people-groups";
    let mut page = 1u32;
    let mut total_pages = 1u32;
    let mut all_groups: Vec<Value> = Vec::new();

    loop {
        let response = client
            .get(base_url)
            .query(&[("page", page.to_string()), ("per_page", "250".to_string())])
            .send()
            .await?;

        total_pages = response
            .headers()
            .get("X-WP-TotalPages")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse().ok())
            .unwrap_or(1);

        let batch: Vec<Value> = response.json().await?;
        all_groups.extend(batch);

        if page >= total_pages {
            break;
        }
        page += 1;
    }

    println!("Fetched {} people groups", all_groups.len());
    Ok(())
}

Using the API - Real-world Use Cases

Plot people groups on an interactive map

Every record includes Latitude and Longitude fields. Fetch a page of groups and drop markers on a Mapbox GL JS or Leaflet map, colored by lostness priority or evangelical status.

// Mapbox GL JS — add people group markers from the API
const response = await fetch(
  "https://peoplegroups.org/wp-json/pg/v1/people-groups?per_page=250"
);
const groups = await response.json();

// Build a GeoJSON FeatureCollection
const geojson = {
  type: "FeatureCollection",
  features: groups
    .filter(g => g.Latitude && g.Longitude)
    .map(g => ({
      type: "Feature",
      geometry: {
        type: "Point",
        coordinates: [parseFloat(g.Longitude), parseFloat(g.Latitude)],
      },
      properties: {
        pgid:    g.PGID,
        name:    g.NmDisp,
        country: g.Ctry,
        lpi:     g.LPI,
        lpiName: g.LPIname,
        pop:     g.Pop,
        religion: g.Rlgn,
      },
    })),
};

// Add as a Mapbox source and layer
map.addSource("people-groups", { type: "geojson", data: geojson });

map.addLayer({
  id: "people-groups-circles",
  type: "circle",
  source: "people-groups",
  paint: {
    // Color by LPI: 1 (pioneer) = red, higher = orange/yellow
    "circle-color": [
      "interpolate", ["linear"], ["get", "lpi"],
      1, "#e63946",
      2, "#f4a261",
      3, "#2a9d8f",
    ],
    "circle-radius": 6,
    "circle-opacity": 0.85,
  },
});

// Show a popup on click
map.on("click", "people-groups-circles", (e) => {
  const p = e.features[0].properties;
  new mapboxgl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(`
      <strong>${p.name}</strong> of ${p.country}<br>
      Population: ${p.pop?.toLocaleString() ?? "Unknown"}<br>
      Religion: ${p.religion}<br>
      Status: ${p.lpiName}
    `)
    .addTo(map);
});

Build a people group dropdown for an application

Populate a <select> element with people group names, using PGID as the value. Filter by country to scope the list for a specific region.

async function populatePeopleGroupSelect(selectElement, countryFilter = null) {
  let page = 1;
  let totalPages = 1;
  const groups = [];

  do {
    const response = await fetch(
      `https://peoplegroups.org/wp-json/pg/v1/people-groups?page=${page}&per_page=250`
    );
    const batch = await response.json();
    groups.push(...batch);
    totalPages = parseInt(response.headers.get("X-WP-TotalPages") ?? "1", 10);
    page++;
  } while (page <= totalPages);

  const filtered = countryFilter
    ? groups.filter(g => g.Ctry === countryFilter)
    : groups;

  filtered.sort((a, b) => a.NmDisp.localeCompare(b.NmDisp));

  selectElement.innerHTML = '<option value="">-- Select a People Group --</option>';
  for (const group of filtered) {
    const option = document.createElement("option");
    option.value = group.PGID;
    option.textContent = `${group.NmDisp} (${group.Ctry})`;
    selectElement.appendChild(option);
  }
}

// Usage
const select = document.getElementById("people-group-select");
await populatePeopleGroupSelect(select, "Nigeria");

select.addEventListener("change", async (e) => {
  if (!e.target.value) return;
  const response = await fetch(
    `https://peoplegroups.org/wp-json/pg/v1/people-groups/${e.target.value}`
  );
  const group = await response.json();
  console.log(group);
});

Display a people group profile card

Fetch a single record by PGID and render a profile card with key statistics, church status, and a photo if available.

async function renderProfileCard(pgid, containerElement) {
  const response = await fetch(
    `https://peoplegroups.org/wp-json/pg/v1/people-groups/${pgid}`
  );

  if (!response.ok) {
    containerElement.innerHTML = "<p>People group not found.</p>";
    return;
  }

  const g = await response.json();

  containerElement.innerHTML = `
    <div class="pg-card">
      ${g.PicURL ? `<img src="${g.PicURL}" alt="${g.NmDisp}" />` : ""}
      <h2>${g.NmDisp} of ${g.Ctry}</h2>
      <p class="pg-pgid">PGID: ${g.PGID}</p>
      <ul class="pg-stats">
        <li><strong>Population:</strong> ${g.Pop?.toLocaleString() ?? "Unknown"}</li>
        <li><strong>Religion:</strong> ${g.Rlgn}</li>
        <li><strong>Language:</strong> ${g.Lang}</li>
        <li><strong>Region:</strong> ${g.RegnSub}, ${g.Regn}</li>
        <li><strong>Evangelical Level:</strong> ${g.EvngLvl}</li>
        <li><strong>Church Planting:</strong> ${g.Plnting}</li>
        <li><strong>Engagement Status:</strong> ${g.EngStat}</li>
      </ul>
      <div class="pg-lostness">
        <strong>${g.LPIname}</strong>
        <p>${g.LPIdesc}</p>
      </div>
      <div class="pg-resources">
        <strong>Resources Available:</strong>
        Bible: ${g.Bible} &nbsp;|&nbsp;
        Jesus Film: ${g.Jesus} &nbsp;|&nbsp;
        Total: ${g.ResTot}
      </div>
      ${g.PicCrdt ? `<p class="pg-photo-credit">${g.PicCrdt}</p>` : ""}
    </div>
  `;
}

// Usage
renderProfileCard("PG012345", document.getElementById("pg-profile"));

Generate a prayer guide entry

Pull lostness and church status fields to generate structured prayer prompts for a daily or weekly prayer guide application.

import requests
from datetime import date

def build_prayer_entry(pgid: str) -> dict:
    response = requests.get(
        f"https://peoplegroups.org/wp-json/pg/v1/people-groups/{pgid}"
    )
    response.raise_for_status()
    g = response.json()

    return {
        "date": date.today().isoformat(),
        "group": g["NmDisp"],
        "country": g["Ctry"],
        "population": g.get("Pop"),
        "religion": g["Rlgn"],
        "language": g["Lang"],
        "status": g.get("LPIname", "Unknown"),
        "church_status": g.get("GSEClng", ""),
        "engagement": g.get("SPIdesc", ""),
        "prompts": [
            f"Pray for the {g['NmDisp']} people of {g['Ctry']}, "
            f"a community of {g.get('Pop', 0):,} primarily practicing {g['Rlgn']}.",
            f"Their status: {g.get('LPIdesc', '')}",
            f"Church situation: {g.get('GSEClng', '')}",
            "Ask God to raise up workers and open doors for the gospel among this people.",
        ],
    }

# Usage
entry = build_prayer_entry("PG012345")
for prompt in entry["prompts"]:
    print(prompt)

Filter for unreached people groups

Use evangelical level and lostness priority fields to identify unreached groups within a specific region, country, or religion family.

import requests

def get_unreached_groups(country: str = None, religion: str = None) -> list:
    base_url = "https://peoplegroups.org/wp-json/pg/v1/people-groups"
    page = 1
    total_pages = 1
    results = []

    while page <= total_pages:
        response = requests.get(base_url, params={"page": page, "per_page": 250})
        response.raise_for_status()

        for group in response.json():
            if group.get("EvngLvl") != "Less than 2%":
                continue
            if country and group.get("Ctry") != country:
                continue
            if religion and group.get("Rlgn") != religion:
                continue
            results.append(group)

        total_pages = int(response.headers.get("X-WP-TotalPages", 1))
        page += 1

    return results

# All unreached groups in Nigeria
nigeria_unreached = get_unreached_groups(country="Nigeria")
print(f"{len(nigeria_unreached)} unreached people groups in Nigeria")

# All unreached Muslim groups
muslim_unreached = get_unreached_groups(religion="Islam")
print(f"{len(muslim_unreached)} unreached Muslim people groups worldwide")

Build a country summary dashboard

Aggregate people group data by country to display a summary of unreached groups, total population, and engagement status — useful for a missions strategy dashboard.

async function buildCountrySummary(countryCode) {
  let page = 1;
  let totalPages = 1;
  const groups = [];

  do {
    const response = await fetch(
      `https://peoplegroups.org/wp-json/pg/v1/people-groups?page=${page}&per_page=250`
    );
    const batch = await response.json();
    groups.push(...batch.filter(g => g.ISOalpha3 === countryCode));
    totalPages = parseInt(response.headers.get("X-WP-TotalPages") ?? "1", 10);
    page++;
  } while (page <= totalPages);

  const unreached = groups.filter(g => g.EvngLvl === "Less than 2%");
  const totalPop  = groups.reduce((sum, g) => sum + (parseInt(g.Pop) || 0), 0);
  const engaged   = groups.filter(g => g.EngStat === "Engaged");

  return {
    country:         groups[0]?.Ctry ?? countryCode,
    totalGroups:     groups.length,
    unreachedCount:  unreached.length,
    totalPopulation: totalPop,
    engagedCount:    engaged.length,
    religionBreakdown: groups.reduce((acc, g) => {
      acc[g.Rlgn] = (acc[g.Rlgn] ?? 0) + 1;
      return acc;
    }, {}),
  };
}

// Usage
const summary = await buildCountrySummary("ETH"); // Ethiopia
console.log(summary);

Field Reference

Field Type Description
PGIDstringPeople Group ID — primary identifier.
PEIDintegerPeople Group Entity ID.
NmDispstringDisplay name.
NmAltstringAlternate names.
ISOalpha3stringISO 3166-1 alpha-3 country code.
CtrystringCountry name.
RegnstringUN region.
RegnSubstringUN sub-region.
LatitudenumberLatitude (decimal degrees).
LongitudenumberLongitude (decimal degrees).
PopintegerPopulation estimate.
ROLstringISO 639-3 language code.
LangstringPrimary language name.
LangFamilystringLanguage family.
LangSpkrsintegerGlobal language speakers.
RORstringReligion of record code.
RlgnstringPrimary religion name.
RlgnDivstringReligion display name.
EvngLvlstringEvangelical level description.
CongExststringWhether congregations exist.
PlntingstringChurch planting activity (last 2 years).
EngStatstringEngagement status.
GSECintegerGreat Commission Status of Evangelization code (1–6).
GSECbrfstringGSEC brief description.
GSEClngstringGSEC long description.
SPIintegerStrategic Priority Index (Engagement Progress).
SPIdescstringSPI description.
LPIintegerLostness Priority Index.
LPInamestringLPI name.
LPIdescstringLPI description.
AffblocstringROP1 Affinity Bloc name.
PplClstrstringPeople cluster name.
PplNmstringROP3 people name.
EthnestringROP25 ethnographic group name.
BiblestringBible availability.
JesusstringJesus Film availability.
ResTotintegerTotal evangelical resources available.
PeopleDescstringPeople group description.
LocationDescstringLocation description.
PicURLstringPhoto URL.
PicCrdtstringPhoto credit.
UpdatedDatestringLast updated (ISO 8601).

Visualize lostness with powerful maps

Access our Maps page to download page or wall-sized maps to catalyze prayer and action in your church, organization, or your own home.

Scroll to Top