Skip to content

GeoNetwork API

The REST API and documentation are available in your catalog at page http://localhost:8080/geonetwork/doc/api/ and linked from the footer on the home page.


GeoNetwork API Access

GeoNetwork API is provided as a self-describing OpenAPI document, browsable in html form, and avaialble as JSON or XML for script interactions.

  1. The API is available from the Admin Console → Catalogue admin tools page:

    • http://localhost:8080/geonetwork/doc/api/index.html

    Catalogue admin tools

  2. The OpenAPI document is browsable as an html page:

    GeoNetwork API OpenAPI document

  3. The page documents each service end-point, including parameters and output results.

    GeoNetwork API Service Endpoint Description

  4. Selecting Server variables at the top of the page allows API testing:

    GeoNetwork API Service Endpoint Test

  5. The OpenAPI document is also available as text/json and text/xml for script access:

    • https://localhost:8080/geonetwork/srv/api/doc
      "openapi": "3.0.1",
      "info": {
        "title": "GeoNetwork 4.0.1 OpenAPI Documentation",
        "description": "This is the description of the GeoNetwork OpenAPI. Use this API to manage your catalog.",
        "contact": {
          "name": "GeoNetwork user mailing list",
          "url": "",
          "email": ""
        "license": {
          "name": "GPL 2.0",
          "url": ""
        "version": "4.0.1"
        "description": "Learn how to access the catalog using the GeoNetwork REST API.",
        "url": "http://localhost:8080/geonetwork/doc/api"

Upgrading from GeoNetwork 3 Guidance

In version 4.0.1 onward the API description is provided using OpenAPI specification:

  • The version of the API correspond to the version of the GeoNetwork instance when the API changed.

  • The GeoNetwork API version number is indicated in the html page title available

  • The GeoNetwork API version number is available to scripts at in the json or xml info description.

  • Previously in GeoNetwork 3 the GeoNetwork API version was included in the path, /srv/api/0.1/\...


Lucene is no longer available, replaced by Elasticsearch.


The GeoNetwork API support for Report Uploads has not been migrated.

Interested parties may contact the project team for guidance and to express their intent.


XLink / Remove directory entry used in other record has not been migrated.

Interested parties may contact the project team for guidance and to express their intent.

Using the API to apply an XSL process

This is an example to trigger an XSL process on a set of records. It illustrates how to make a set of actions using the API:


rm -f /tmp/cookie;
curl -s -c /tmp/cookie -o /dev/null \
  -X GET \
  -H "Accept: application/json" \
TOKEN=`grep XSRF-TOKEN /tmp/cookie | cut -f 7`;
curl \
  -X GET \
  -H "Accept: application/json" \
  -H "X-XSRF-TOKEN: $TOKEN" --user $CATALOGUSER:$CATALOGPASS -b /tmp/cookie \

# MUST return user details

curl -X POST "$CATALOG/srv/api/search/records/_search?bucket=111" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json;charset=utf-8' \
    -H "X-XSRF-TOKEN: $TOKEN" -c /tmp/cookie -b /tmp/cookie --user $CATALOGUSER:$CATALOGPASS \
    -d '{"from":0,"size":0,"query":{"query_string":{"query":"+linkUrl:*data-and-maps*"}}}'

curl -X PUT "$CATALOG/srv/api/selections/111" -H "accept: application/json" \
  -H "X-XSRF-TOKEN: $TOKEN" -c /tmp/cookie -b /tmp/cookie --user $CATALOGUSER:$CATALOGPASS
#Body response = number of selected records

curl -X GET "$CATALOG/srv/api/selections/111" -H "accept: application/json" \
  -H "X-XSRF-TOKEN: $TOKEN" -c /tmp/cookie -b /tmp/cookie --user $CATALOGUSER:$CATALOGPASS
#Body returns an array of selected records

curl -X POST "$CATALOG/srv/api/processes/$PROCESS?bucket=111&index=false" \
  -H "accept: application/json" -H "X-XSRF-TOKEN: $TOKEN" -c /tmp/cookie -b /tmp/cookie --user $CATALOGUSER:$CATALOGPASS

Loop on search results and apply changes (processing and batch editing)

This is an example to highlight how to loop over specific search results (here only series) and apply various changes:



rm results.json
rm -f /tmp/cookie;

curl -s -c /tmp/cookie -o /dev/null \
  -X GET \
  -H "Accept: application/json" \

TOKEN=`grep XSRF-TOKEN /tmp/cookie | cut -f 7`;
JSESSIONID=`grep JSESSIONID /tmp/cookie | cut -f 7`;

curl "$SERVER/srv/api/search/records/_search" \
    -X 'POST' \
    -H 'Accept: application/json, text/plain, */*' \
    -H 'Content-Type: application/json;charset=UTF-8' \
    --data-raw "{\"query\":{\"query_string\":{\"query\": \"+isHarvested:false +resourceType: $type\"}},\"from\":$from, \"size\":$size, \"_source\": {\"include\": [\"resourceTitleObject.default\"]}, \"sort\": [{\"resourceTitleObject.default.keyword\": \"asc\"}]}" \
    --compressed \
    -o results.json

for hit in $(jq -r '.hits.hits[] | @base64' results.json); do
   _jq() {
     echo "${hit}" | base64 --decode | jq -r "${1}"

  title=$(_jq '._source.resourceTitleObject.default')
  uuid=$(_jq '._id')
  echo "__________"
  echo "### $uuid"

  # Update series from its members using XSL process
  curl $AUTH "$SERVER/srv/api/records/$uuid/processes/collection-updater" \
    -X 'POST' \
    -H 'Accept: application/json, text/plain, */*' \

  curl $AUTH "$SERVER/srv/api/selections/s101" \
    -X 'DELETE' \
    -H 'Accept: application/json, text/javascript, */*; q=0.01' \

  curl $AUTH "$SERVER/srv/api/selections/s101?uuid=$uuid" \
    -X 'PUT' \
    -H 'Accept: application/json, text/javascript, */*; q=0.01' \

  # Keep only the first 2 resource identifiers using batch editing
  curl $AUTH "$SERVER/srv/api/records/batchediting?bucket=s101" \
    -X 'PUT' \
    -H 'Accept: application/json, text/plain, */*' \
    -H 'Content-Type: application/json;charset=UTF-8' \
    --data-raw "[{\"xpath\":\"/gmd:identificationInfo/*/gmd:citation/*/gmd:identifier[position() > 2]\",\"value\":\"<gn_delete/>\"}]" \

Using the search API in Google sheet

In Extensions → App script create a new function. Here we create a function which run a search and return a list of matching UUIDs:

function getUuidForSearch(query) {
  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : "{\"query\":{\"query_string\":{\"query\":\"" + query + "\"}}}"
  var response = UrlFetchApp.fetch('http://localhost:8080/catalogue/srv/api/search/records/_search', options);
  var hits = JSON.parse(response).hits;
  return hits.hits.length > 0 ? {return v._id}).join('###') : null;

Then use the function in formula. Here we search for records matching particular keywords:

Building client for the API using codegen

The API is described using the Open API specification. Codegen is a tool to build an API client based on the specification. To build a Java client use the following procedure.

First, create a configuration file apiconfig.json for the API:

java -jar swagger-codegen-cli.jar generate \
     -i http://localhost:8080/geonetwork/srv/v2/api-docs \
     -l java \
     -c apiconfig.json \
     -o /tmp/gn-openapi-java-client

cd /tmp/gn-openapi-java-client

mvn clean install

Once compiled, the Java client can be used as a dependency; eg. for Maven:


Then the client API can be used in your Java application:

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import org.fao.geonet.ApiClient;
import org.fao.geonet.ApiException;
import org.fao.geonet.Configuration;
import org.fao.geonet.openapi.MeApi;
import org.fao.geonet.openapi.RecordsApi;
import org.fao.geonet.openapi.model.MeResponse;
import org.fao.geonet.openapi.model.SimpleMetadataProcessingReport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import java.util.Arrays;
import java.util.Base64;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class GnJavaApiClientTest {

    private static final String CATALOGUE_URL = "http://localhost:8080/geonetwork";

    ApiClient client;

    private static final String USERNAME = "admin";
    private static final String PASSWORD = "admin";

    private void initConfiguration() {
            = Configuration.getDefaultApiClient();

     * Get user information when anonymous or connected.
    public void getMeInfoTest() {
        try {

            MeApi meApi = new MeApi();
            MeResponse meResponse = meApi.getMe();
            // User is not authenticated
            assertEquals(null, meResponse);

            // Configure HTTP basic authorization: basicAuth
            client.getHttpClient().networkInterceptors().add(new BasicAuthInterceptor(USERNAME, PASSWORD));

            meResponse = meApi.getMe();
            // User is authenticated
            assertEquals(USERNAME, meResponse.getName());

        } catch (ApiException e) {

     * Insert and delete a record.
    public void insertAndDeleteRecord() {

        // Configure HTTP basic authorization: basicAuth
        client.getHttpClient().networkInterceptors().add(new BasicAuthInterceptor(USERNAME, PASSWORD));

        try {
            final RecordsApi api = new RecordsApi();

            SimpleMetadataProcessingReport report = api.insert("METADATA",
                Arrays.asList(new String[]{""}),
                null, null,
                true, "NOTHING",

            int nbOfRecordInserted = report.getMetadataInfos().size();

            // One record MUST be inserted
            assertEquals(1, nbOfRecordInserted);

            if (nbOfRecordInserted == 1) {
                Object[] list = report.getMetadataInfos().keySet().toArray();
                String metadataId = (String) list[0];
                String record = api.getRecord(metadataId, "application/xml");

                api.deleteRecord(metadataId, false);

                try {
                    api.getRecord(metadataId, "application/xml");
                } catch (ApiException e) {
                    assertEquals(404, e.getCode());
        } catch (ApiException e) {

     * Interceptor to add basic authentication header on each request.
     * <p>
     * TODO: How-to make generated client taking care of setting BA from swagger config.
     * TODO: Add support for CSRF token.
    public class BasicAuthInterceptor implements Interceptor {
        String username;
        String password;

        public BasicAuthInterceptor(String username, String password) {
            this.username = username;
            this.password = password;

        public Response intercept(Interceptor.Chain chain) throws IOException {
            byte[] auth = Base64.getEncoder()
                .encode((username + ":" + password).getBytes());

            Request compressedRequest = chain.request().newBuilder()
                .header("Authorization", "Basic " + new String(auth))

            return chain.proceed(compressedRequest);

Connecting to the API with python

This is an example of how to use requests in python to authenticate to the API and generate an XRSF token.

import requests

# Set up your username and password:
username = 'username'
password = 'password'

# Set up your server and the authentication URL:
server = "http://localhost:8080"
authenticate_url = server + '/geonetwork/srv/eng/info?type=me'

# To generate the XRSF token, send a post request to the following URL: http://localhost:8080/geonetwork/srv/eng/info?type=me
session = requests.Session()
response =

# Extract XRSF token
xsrf_token = response.cookies.get("XSRF-TOKEN")
if xsrf_token:
    print ("The XSRF Token is:", xsrf_token)
    print("Unable to find the XSRF token")

# You can now use the username and password, along with the XRSF token to send requests to the API.

# This example will add an online resource to a specified UUID using the http://localhost:8080/geonetwork/srv/api/records/batchediting endpoint

# Set header for connection
headers = {'Accept': 'application/json',
'X-XSRF-TOKEN': xsrf_token

# Set the parameters
params = {'uuids': 'the uuid to be updated',
'bucket': 'bucketname',
'updateDateStamp': 'true',

# Set the JSON data: note that the value must have one of <gn_add>, <gn_create>, <gn_replace> or <gn_delete>
json_data = [{'condition': '',
'value': '<gn_add><gmd:onLine xmlns:gmd="" xmlns:gco=""><gmd:CI_OnlineResource><gmd:linkage><gmd:URL>https://localhost</gmd:URL></gmd:linkage><gmd:protocol><gco:CharacterString>WWW:LINK-1.0-http--link</gco:CharacterString></gmd:protocol><gmd:name><gco:CharacterString>The Title of the URL</gco:CharacterString></gmd:name><gmd:description><gco:CharacterString>The description of the resource</gco:CharacterString></gmd:description><gmd:function></gmd:function></gmd:CI_OnlineResource></gmd:onLine></gn_add>',
'xpath': '/gmd:MD_Metadata/gmd:distributionInfo/gmd:MD_Distribution/gmd:transferOptions/gmd:MD_DigitalTransferOptions',

# Send a put request to the endpoint
response = session.put(server + 'geonetwork/srv/api/records/batchediting',
auth = (username, password),