Getting started with the Data API
Section titled “Getting started with the Data API”The Refugee Data Portal Data API gives programmatic access to all of the data that is published on the portal. It is a read-only service: it lets analysts, researchers and developers access the data and metadata, query it with filters, and download complete data series, without visiting the data finder.
This guide introduces how the API works and walks through the most common tasks with examples that can be copied and run as they are. It complements the interactive technical reference, which lists every endpoint, parameter and response field.
Before you start
Section titled “Before you start”- Base URL. All examples in this guide use
https://api-data.unhcr.org/api/v0. - No account is required. The API is open and needs no key or token.
- Responses are JSON, except for downloads, which are CSV files.
- Every request is rate-limited per client. Data queries allow 60 requests per minute, downloads 10 per minute, and catalogue, constraints and metadata requests 120 per minute. Requests over the limit receive an HTTP
429response and should be retried after a short pause.
Intended use
Section titled “Intended use”The Data API is provided to support the analysis and extraction of the data published on the Refugee Data Portal. It is designed for analysts, researchers, journalists and developers who need to retrieve data series for their own analysis, reports, research or occasional integration into tools and scripts.
The API is not designed to serve as a data backend for other products or services. In particular, it is not intended for:
- production systems that depend on high availability or guaranteed response times;
- continuous or automated high-volume access, such as scheduled bulk extraction or frequent polling for changes;
- applications, dashboards or services offered to third parties that rely on the API for their operation.
If you would like to access the data from the portal for one of these purposes, please reach out to us.
Service level. The API is offered as a best-effort service. Availability, response times and rate limits are not guaranteed and may change without prior notice. Endpoints and response formats under the current version may also change.
Fair use. Requests are rate-limited per client, as described above. Clients are expected to request one page at a time, to pause between requests, to use the download endpoint rather than paginated queries to obtain complete data series, and to keep a local copy of data that is used repeatedly rather than requesting it again.
Advanced requirements. Organisations that need a higher rate limit, a service-level commitment, continuous or large-scale access, or that plan to build a product on top of UNHCR data are invited to contact UNHCR at [contact channel] to discuss their requirements.
Attribution. Data retrieved through the API is subject to the same terms of use and attribution requirements as the data published on the portal. See [terms of use page].
Two ways to retrieve data
Section titled “Two ways to retrieve data”The API offers two complementary ways to obtain data. Choosing the right one is the single most important decision when using the service, and the most common source of incomplete results.
| Need | Use | What is returned |
|---|---|---|
| All the data published as part of a data series | GET /data/export/{code} | One CSV file with every row of the data series, in a single download |
A selection of records filtered by the dimensions of interest such as population type (pop_type), country of asylum (ref_area_adm0), country of origin (area_ori_adm0), time range (time_period), etc. | GET /data/{code} | JSON, paginated: at most 100 records per request, with filters and column selection |
To obtain the complete set of data associated with a data series, use the download endpoint. It streams the full data series in one request and does not require pagination. At present it does not accept filters: the file always contains the whole data series. A filtered download will be made available in a forthcoming release.
The query endpoint never returns the full data series in one response. It returns one page at a time, 20 records by default and 100 at most. A client that reads only the first page will miss the rest of the data. The section Retrieving all matching records explains how to walk through every page.
A complete walk-through
Section titled “A complete walk-through”The examples below use the official population statistics data series, whose code is PopStatsOfficial. The same steps apply to every data series.
1. Find the data series
Section titled “1. Find the data series”The catalogue lists every data series with its code, labels and definition. It supports free-text search and filters by dataset, topic, country, region, population type and framework.
GET https://api-data.unhcr.org/api/v0/catalog?q=population%20statisticsEach entry in array carries the data series code together with ready-made links to its data, metadata and constraints:
{ "code": "PopStatsOfficial", "label_short": "Official population statistics on people affected by forced displacement and statelessness", "dataset": "POP", "data_url": ".../api/v0/data/PopStatsOfficial", "metadata_url": ".../api/v0/metadata/PopStatsOfficial", "constraints_url": ".../api/v0/constraints/PopStatsOfficial"}The catalogue is itself paginated (page, page_size), so a search that returns more than one page must be walked in the same way as data queries.
2. Learn how the data series is structured
Section titled “2. Learn how the data series is structured”Before querying, ask the API which dimensions the data series has, which values each dimension accepts, and which units are available. This is the reference for every filter used later.
GET https://api-data.unhcr.org/api/v0/constraints/PopStatsOfficialThe response describes, among other things:
dimensions: one entry per dimension, with itsroleand the list of validvalues. ForPopStatsOfficialthe dimensions includepop_type,ref_area_adm0(country of asylum),area_ori_adm0(country of origin),sex,age,urbanisationandsetting.units.defaultandunits.available: the unit that queries return unless another one is requested, and the alternatives.time_period.granularity: whether the data series is reported by year or by month, which determines how time filters are written.
Two special values appear at the top of many dimensions:
_T(Total) selects the aggregated record, for example both sexes together._X(Unspecified) selects records for which the dimension was not reported.
Population type codes follow the population definitions used across the portal, for example REF_UNHCR for refugees under UNHCR's mandate, REF_UNRWA for refugees under UNRWA's mandate, OIP for other's in need of international protection and ASY for asylum-seekers.
3. Query a selection of records
Section titled “3. Query a selection of records”Filters use the SDMX 3.0 convention c[dimension]=value. Several values for the same dimension are separated by commas and combined with OR. Different dimensions are combined with AND.
The request below returns the number of refugees under UNHCR's mandate in Colombia from 2025 onwards, one record per reporting period:
GET https://api-data.unhcr.org/api/v0/data/PopStatsOfficial?c[pop_type]=REF_UNHCR&c[ref_area_adm0]=COL&time_period=ge:2025&dimensions=default{ "data": [ { "time_period": "2025-12", "pop_type": "REF_UNHCR", "pop_type_label": "Refugees [UNHCR]", "ref_area_region_unhcr": "RBA", "ref_area_region_unhcr_label": "Regional Bureau for the Americas", "ref_area_adm0": "COL", "ref_area_adm0_label": "Colombia", "unit_measure": "IND", "unit_mult": 1, "unit_comp": "TOT", "obs_value": 1464 } ], "pagination": { "page": 1, "page_size": 20, "total_rows": 1, "total_pages": 1, "unfiltered_total_rows": 650016, "total_rows_estimated": false, "has_next": false }}Every response includes a query block echoing the parameters that were applied, a data array with the records, and a pagination block. Each coded column is accompanied by a _label column with its human-readable name.
The dimensions parameter controls the level of detail:
- omitted: every record stored for the data series, at every level of disaggregation;
default: the data series' headline view, keeping only its required and default dimensions (here period, population type, region and country) and returning the totals;- a comma-separated list: a custom breakdown. The list must include every dimension the data series marks as
requiredin its constraints and every dimension used in a filter; the remaining names add the breakdown.
For example, the refugee population of Uganda from 2025 onwards, disaggregated by sex. pop_type is required in PopStatsOfficial and ref_area_adm0 is filtered, so both stay in the list and in the output; sex is the breakdown:
GET https://api-data.unhcr.org/api/v0/data/PopStatsOfficial?c[pop_type]=REF_UNHCR&c[ref_area_adm0]=UGA&time_period=ge:2025&dimensions=pop_type,ref_area_adm0,sex| time_period | pop_type | ref_area_adm0 | sex | obs_value |
|---|---|---|---|---|
| 2025-12 | REF_UNHCR | UGA | F | 991,958 |
| 2025-12 | REF_UNHCR | UGA | M | 944,761 |
| 2025-12 | REF_UNHCR | UGA | _T | 1,936,719 |
Records can be ordered with sort=field:asc or sort=field:desc, for instance sort=obs_value:desc to rank countries by population.
4. Download the complete data series
Section titled “4. Download the complete data series”GET https://api-data.unhcr.org/api/v0/data/export/PopStatsOfficialThe response is a CSV file (PopStatsOfficial.csv) with one row per data point and the same coded and labelled columns as the query endpoint. Large data series run to hundreds of thousands of rows, so the download should be saved to disk with a command-line tool or a script rather than opened in the interactive reference:
curl -L -o PopStatsOfficial.csv "https://api-data.unhcr.org/api/v0/data/export/PopStatsOfficial"5. Read the metadata
Section titled “5. Read the metadata”The metadata endpoint returns the data series' definition, rationale, method of computation, comparability notes, limitations, data sources, related frameworks and coverage.
GET https://api-data.unhcr.org/api/v0/metadata/PopStatsOfficialThe same information is available as a CSV file from GET /metadata/export/PopStatsOfficial.
Retrieving all matching records with the query endpoint
Section titled “Retrieving all matching records with the query endpoint”A query is answered one page at a time. To retrieve every record that matches a set of filters:
- Request
page=1withpage_size=100, the maximum. - Read
pagination.has_next. While it istrue, request the nextpagewith exactly the same filters, dimensions and sort. - Stop when
has_nextisfalse.
has_next is always exact, so it is the safest signal to stop on. total_rows and total_pages are exact in almost all cases, but for some custom multi-dimension breakdowns the first request returns an estimate, flagged by total_rows_estimated: true, until the exact count is available a few seconds later.
Three rules keep a client within the rate limit while walking pages:
- One request at a time. Request the next page only after the previous one has been received; never fetch pages in parallel.
- Pause between pages. A one-second pause keeps the client under the rate-limit.
- Wait and retry on
429. When the limit is exceeded, wait a few seconds and request the same page again rather than abandoning the walk.
Check total_rows on the first page before walking. If it runs to tens of thousands of records, the download endpoint is the right tool: at 100 records per request and one request per second, an entire data series of several hundred thousand records would take hours through the query endpoint, while the download returns it in a single request.
A minimal Python example that follows these rules:
import timeimport requests
BASE = "https://api-data.unhcr.org/api/v0"params = { "c[pop_type]": "REF_UNHCR", "time_period": "ge:2025", "dimensions": "default", "page_size": 100, "page": 1,}
rows = []while True: response = requests.get(f"{BASE}/data/PopStatsOfficial", params=params, timeout=60) if response.status_code == 429: time.sleep(10) # over the limit: wait, then retry the same page continue response.raise_for_status() body = response.json() rows.extend(body["data"]) if not body["pagination"]["has_next"]: break params["page"] += 1 time.sleep(1) # one request per second stays within the limit
print(f"{len(rows)} records retrieved")Why a query returns fewer records than expected
Section titled “Why a query returns fewer records than expected”When a result looks incomplete, the cause is almost always one of the following.
- Only the first page was read. The default page holds 20 records. Check
pagination.has_nextandtotal_rows, and walk through every page as described above. To get the whole data series, use the download endpoint instead. - Only the default unit is returned. A query returns records for one unit at a time, the data series' default unless
unit=is given.constraints.units.availablelists the alternatives with their record counts.unfiltered_total_rowsin every response is the size of the full data series for the unit in use. - The
dimensionsparameter changed the level of detail.dimensions=defaultreturns the headline totals only. Omittingdimensionsreturns every disaggregated record. A custom list returns one record per combination of the listed dimensions. - A filtered dimension was left out of
dimensions. The API rejects the request with400 INVALID_FILTERrather than silently dropping the filter. Add the dimension to the list. - A code did not match. Filter values must be the exact codes advertised by the constraints endpoint.
REFreturns no records inPopStatsOfficial;REF_UNHCRdoes. A wrong dimension name is rejected with the list of valid dimensions in the error details. - The time filter was written incorrectly. Time filters take the form
time_period=op:year, whereopis one ofeq,ge,gt,le,lt, for exampletime_period=ge:2025. Two conditions are joined with+to form a range, for exampletime_period=ge:2020+le:2023. A year filter covers every period reported within that year, whatever the data series' granularity. - The value is stored as
_X(Unspecified). Records without a reported value for a dimension carry the_Xcode. Filtering on a specific category excludes them. Filtering on_Xselects them.
Errors
Section titled “Errors”Every error uses the same envelope, with a machine-readable code and a plain-language message:
{ "error": { "status": 404, "code": "INDICATOR_NOT_FOUND", "message": "Indicator 'DoesNotExist' not found", "details": {} }}| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_FILTER, INVALID_PARAMETER | A filter, dimension or parameter is not valid. details lists what is accepted. |
| 404 | INDICATOR_NOT_FOUND | The indicator code does not exist. Check it in the catalogue. |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests. Wait a moment and retry. |
| 503 | SERVICE_UNAVAILABLE | The service is starting up. Retry shortly. |
Quick reference
Section titled “Quick reference”| Endpoint | Purpose | Paginated |
|---|---|---|
GET /catalog | Search and list data series | Yes |
GET /metadata/{code} | Data series definition, sources and coverage | No |
GET /metadata/export/{code} | Same metadata as a CSV file | No |
GET /constraints/{code} | Dimensions, valid values and units of a data series | No |
GET /data/{code} | Filtered query of a data series | Yes, 100 records max |
GET /data/export/{code} | Complete data series as a CSV file | No |
Parameters of GET /data/{code}:
| Parameter | Example | Notes |
|---|---|---|
c[dimension] | c[ref_area_adm0]=COL,PER | One or more values, comma-separated. Repeat for each dimension. |
time_period | ge:2025 or ge:2020+le:2023 | Operators eq, ge, gt, le, lt, applied to a year. |
unit | IND.1.TOT | Defaults to the data series' default unit. |
dimensions | default or pop_type,sex | Column selection and level of detail. |
sort | obs_value:desc | One or more field:direction pairs. |
page, page_size | page=2&page_size=100 | page_size is 20 by default and 100 at most. |
footnotes | true | Adds source and annotation columns. |
The full specification of every parameter and response field is available in the interactive technical reference.
Please reach out directly for any questions or concerns.