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.

In version 4, the API description is using OpenAPI specification. Old path to the API including the version /srv/api/0.1/… is replaced by /srv/api/…. The version of the API correspond to the version of the GeoNetwork instance.

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:

export CATALOG=http://localhost:8080/geonetwork
export CATALOGUSER=admin
export CATALOGPASS=admin
export PROCESS=migrate-201904

rm -f /tmp/cookie;
curl -s -c /tmp/cookie -o /dev/null \
  -X GET \
  -H "Accept: application/json" \
export 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

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);