Enrich API data with custom curation in API hub


This tutorial shows you how to enrich API data in Apigee API hub with API specifications, like OpenAPI specs, that are stored outside of API hub. You'll use API hub's custom curation features to automatically fetch API specifications from a Cloud Storage bucket and associate them with their corresponding Apigee API proxies. The logic for this custom curation is defined using an integration in Application Integration.

Throughout the tutorial, you'll set up the required Google Cloud services, configure a custom curation integration, create a new plugin instance in API hub, and verify the enriched API data.

Tutorial flow diagram

Objectives

In this tutorial, you will complete the following steps:

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator.

New Google Cloud users might be eligible for a free trial.

When you finish the tasks that are described in this document, you can avoid continued billing by deleting the resources that you created. For more information, see Clean up.

Before you begin

Before you begin this tutorial, ensure you have completed the following:

Prepare the environment

Now, lets prepare the environment for our custom curation. This involves creating the necessary API proxies in Apigee and setting up Cloud Storage with your API specification files.

Create API proxies in Apigee

To get started, you'll first need to register the following sample API proxies in your Apigee project:

API proxy name Configuration details
Base config Revision config
Orders API
  • Proxy template: Reverse proxy
  • Target: https://mocktarget.apigee.net
Change target endpoint to: https://mocktarget.apigee.net/xml
Products API
Users API

For more information about how to register a proxy in Apigee, see Create an API proxy.

Set up Cloud Storage

Next, you'll set up Cloud Storage by creating a bucket and uploading the sample API specification files that will be used in the custom curation process.

Create Cloud Storage bucket

To create a Cloud Storage bucket that will hold your API specifications, do the following:

  1. In the Google Cloud console, go to the Cloud Storage Buckets page.

    Go to Buckets

  2. Click Create.
  3. On the Create a bucket page, enter curation_bucket as the bucket name.
  4. Click Create.

    A confirmation dialog appears to set the access level of the bucket.

  5. Click Confirm.

    A new Cloud Storage bucket named curation_bucket is created and is listed in the Buckets page.

Upload API specification files

With your Cloud Storage bucket ready, you can now upload the sample API specification files.

Download the following specification files and use them for the purpose of this tutorial:

Spec file Description Download link
orders-api.yaml This specification defines an API for managing customer orders. Click to download
products-api.yaml This specification describes an API for managing products. Click to download
users-api.yaml This specification outlines an API for managing user accounts. Click to download

To upload the API specification files into your Cloud Storage bucket, do the following:

  1. In the Google Cloud console, go to the Cloud Storage Buckets page.

    Go to Buckets

  2. Click the curation_bucket bucket that you created to open the Bucket details page.
  3. In the Objects tab, click Upload > Upload files.
  4. In the file dialog, go to the API specification files that you downloaded and select them. After the upload completes, you should see them listed in your bucket folder browser.

Configure Cloud Storage connection

To allow your curation integration to access the files in your bucket, you will now configure a Cloud Storage connection in Integration Connectors.

To configure a Cloud Storage connection, do the following:

  1. In the Google Cloud console, go to the Integration Connectors page.

    Go to Integration Connectors

  2. Click Connections from the left navigation menu to open the Connections page.
  3. Click Create New and provide the following details in the Create Connection page:

    1. Location: select a desired location and click Next.
    2. Connector: select Cloud Storage from the drop-down list and click Next.
    3. Connector version: enter the version of your connector.
    4. Connection name: enter fetch-specs.
    5. Service Account: select the service account that you had created earlier.
    6. Project ID: enter the ID of your Google Cloud project. For information about how to find your project ID, see Find the project name, number, and ID.
  4. Review your connection details and click Create. A new connection is created and is listed in the Connections page.

Step 1: Create an integration for custom curation

With the environment ready, you can now define the custom curation logic using Application Integration, which will handle the process of identifying, enriching, and transforming the API metadata.

Download the following prebuilt integration JSON file and use it for the purpose of this tutorial:

Integration file Download link
enrich-with-spec-yaml.json Click to download

Create and upload an integration

To begin, you'll create a new integration in Application Integration and then upload the downloaded JSON file to define the curation logic:

  1. In the Google Cloud console, go to the Application Integration page.

    Go to Application Integration

  2. Click Integrations from the left navigation menu to open the Integrations page.
  3. Click Create integration and provide the following details in the Create Integration page:
    1. Integration name: enter test-curation.
    2. Description: enter Demo integration created for custom curation tutorial.
    3. Region: select a desired location .
    4. Click Create. The new integration opens in the integration editor.
  4. In the designer toolbar, click (Actions menu) and select Upload.
  5. In the file browser dialog, select the downloaded JSON file, and then click Open.

    A new version of the integration is created as shown in the following image:

Sample integration image

Configure connector task in the integration

After uploading the integration, the next step is to configure the Connectors task to connect to your Cloud Storage bucket:

  1. Click the Connectors task named Fetch spec from GCS to view the connector configuration pane.
  2. Click Configure Connector.
  3. Configure the following details in the Connectors Task Editor page:
    1. Region: select a desired location .
    2. Connection: Select fetch-specs.
    3. Type: Select Actions.
    4. Set entities/actions: Select DownloadObject as the action.
    5. Click Done.

View integration components

The integration you uploaded contains several pre-configured components. The following table provides details about each element to help you understand how the curation logic works:

Component Configuration Description
API trigger API Trigger ID: api_trigger/test-custom-curation_API_1 Receives data from the API hub plugin instance to invoke the integration for custom curation.
For Each Loop Task

(For Each API)

List to iterate: apiData.apiMetadataList.apiMetadata

Sub-integration details:

  • Integration name: enrich-with-spec-yaml
  • Trigger ID: private_trigger/test-curate-1_Private_1
Iterates over the apiMetadata list and calls the sub-integration for each API resource. It also collates the response of each run in curatedAPIMetadataList, where each element of the array has the response from one particular run.
Private Trigger

(Enrich each API with spec)

Trigger ID: private_trigger/test-curate-1_Private_1 Invokes the sub-integration for each element in apiMetaData.
Data mapping task

(Construct GCS request payload)

Input:
connectorInputPayload (Fetch spec from GCS)
.SET_PROPERTY(
apiMetadata
.GET_PROPERTY("api")
.GET_PROPERTY("displayName")
.TO_STRING()
.CONCAT(".yaml")
.TO_LOWERCASE()
   ,  
"ObjectFilePath"
)
    
Output: ConnectorInputPayload (Fetch spec from GCS)
Constructs the input payload for ConnectorInputPayload.
Connectors task

(Fetch spec from GCS)

Task input: connectorInputPayload (Fetch spec from GCS)

Task output: connectorOutputPayload (Fetch spec from GCS)

Connects to a Cloud Storage bucket created and performs file transfer operations.
Edge condition

(Fetched spec successfully)

Condition: $`ErrorInfo`.code$ = 0 Checks for successful connection execution and payload retrieval. If yes, proceeds to parse the spec contents.
Edge condition

(Failed to fetch spec)

Condition: $`ErrorInfo`.code$ != 0 Checks the connectorOutputPayload for empty payload; if yes, skips the curation logic.
Data mapping task

(Parse spec contents)

Input 1:
connectorOutputPayload (Fetch spec from GCS)
.GET_ELEMENT(
"0"
)
    
Output 1: gcsResponseTemp

Input 2:

gcsResponseTemp
.GET_PROPERTY(
"Content"
)
.TO_STRING()
.TO_BASE_64()
    

Output 2: specContent
Parses the API specification content from the connectorOutputPayload and encodes it to base64 format.
JavaScript task

(Enrich API metadata with spec contents)

Script:

/**
 * Processes and curates API metadata with spec contents.
 * @param {object} event 
 * The event object containing API data and spec content.
 */
function executeScript(event) {
  let apiMetadata = event.getParameter('apiMetadata');
  let specContent = event.getParameter('specContent');

  // Initialize curatedAPIMetadata directly from apiMetadata
  const curatedAPIMetadata = { ...apiMetadata
  };

  // Construct the 'spec' object efficiently
  const spec = {
    displayName: "open-api-spec",
    specType: {
      enumValues: {
        values: [{
          id: "openapi"
        }]
      }
    },
    contents: {
      mimeType: "application/yaml",
      contents: specContent,
    },
  };

  // Create the specMetadata object
  const specMetadata = {
    // Ensure originalUpdateTime exists before assigning
    // provide a fallback if needed
    originalModifiedTime: curatedAPIMetadata.originalUpdateTime 
    || new Date().toISOString(),
    spec: spec,
  };

  // Initialize or update the specs array within the first version
  // This assumes versions[0] always exists. Add checks if it might not.
  if (!curatedAPIMetadata.versions || 
    curatedAPIMetadata.versions.length === 0) {
    curatedAPIMetadata.versions = [{
      specs: []
    }];
  } else if (!curatedAPIMetadata.versions[0].specs) {
    curatedAPIMetadata.versions[0].specs = [];
  }

  curatedAPIMetadata.versions[0].specs.push(specMetadata);

  // Set the modified apiData parameter
  event.setParameter('curatedAPIMetadata', curatedAPIMetadata);
}
    
The script demonstrates how the API metadata is enriched using the parsed specContent output from the Data Mapping task. The function constructs an updated curatedAPIMetadata object. It structures the provided specification content into a new spec object, identifies it as an OpenAPI YAML, and embeds it into a specMetadata object along with its modification timestamp. This specMetadata is then integrated into the specs array of the API's first version within curatedAPIMetadata, effectively linking the API proxy with its detailed specification. Finally, the enriched curatedAPIMetadata is returned through the event object for subsequent steps in the custom curation workflow.
JavaScript task

(Do not enrich API metadata)

Script:
function executeScript(event) {
  // Retrieve the apiData parameter.
  let apiMetadata = event.getParameter('apiMetadata');


  // Set the curated API metadata parameter same as API data.
  event.setParameter('curatedAPIMetadata', apiMetadata);
}
    
The script sets the curated API metadata parameter to be the same as the API data.
Data mapping task

(Set curated API data)

Input:
apiData
.SET_PROPERTY(
curatedAPIMetadataList,  
"apiMetadataList.apiMetadata"
)
    
Output: apiData
Constructs the curated apiData which is configured as the response payload of the integration.

Test and publish the integration

With the configuration complete, you can now test the integration with a sample input to ensure it's working correctly before publishing it for use in API hub. To test the integration, click Test in the integration editor toolbar.

Sample test input

Use the following sample input:

{
  "apiMetadataList": {
    "apiMetadata": [
      {
        "api": {
          "name": "projects/api-hub-demo-5/locations/us-central1/apis/api-hub-demo-5-Products-API",
          "displayName": "Products-API",
          "fingerprint": "products-api"
        },
        "versions": [
          {
            "version": {
              "name": "projects/api-hub-demo-5/locations/us-central1/apis/api-hub-demo-5-Products-API/versions/version-1",
              "displayName": "version-1"
            },
            "deployments": [
              {
                "deployment": {
                  "displayName": "Products-API",
                  "description": "API for managing product inventory. This API allows for the creation, retrieval, update, and deletion of product records.",
                  "deploymentType": {
                    "enumValues": {
                      "values": [
                        {
                          "id": "apigee"
                        }
                      ]
                    },
                    "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/system-deployment-type"
                  },
                  "resourceUri": "organizations/api-hub-demo-5/apis/Products-API/revisions/1/environments/test-env",
                  "endpoints": [
                    "https://googleapis.com/products-api"
                  ],
                  "attributes": {
                    "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-environment": {
                      "stringValues": {
                        "values": [
                          "test-env"
                        ]
                      },
                      "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-environment"
                    },
                    "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-organization": {
                      "stringValues": {
                        "values": [
                          "api-hub-demo-5"
                        ]
                      },
                      "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-organization"
                    }
                  }
                },
                "originalId": "apis/Products-API/revisions/1/environments/test-env",
                "originalCreateTime": "2025-07-07T05:23:51.617Z",
                "originalUpdateTime": "2025-07-07T05:23:51.617Z"
              }
            ],
            "originalId": "apis/Products-API/revisions/1",
            "originalCreateTime": "2025-07-07T05:23:47.982Z",
            "originalUpdateTime": "2025-07-07T05:23:47.982Z"
          }
        ],
        "originalId": "apis/Products-API",
        "originalUpdateTime": "2025-07-07T05:23:47.982Z"
      }
    ]
  }
}

Sample Output

You should see the following test output:

{
  "apiMetadataList": {
    "apiMetadata": [
      {
        "api": {
          "name": "projects/api-hub-demo-5/locations/us-central1/apis/api-hub-demo-5-Products-API",
          "displayName": "Products-API",
          "fingerprint": "products-api"
        },
        "versions": [
          {
            "version": {
              "name": "projects/api-hub-demo-5/locations/us-central1/apis/api-hub-demo-5-Products-API/versions/version-1",
              "displayName": "version-1"
            },
            "deployments": [
              {
                "deployment": {
                  "displayName": "Products-API",
                  "description": "API for managing product inventory. This API allows for the creation, retrieval, update, and deletion of product records.",
                  "deploymentType": {
                    "enumValues": {
                      "values": [
                        {
                          "id": "apigee"
                        }
                      ]
                    },
                    "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/system-deployment-type"
                  },
                  "resourceUri": "organizations/api-hub-demo-5/apis/Products-API/revisions/1/environments/test-env",
                  "endpoints": [
                    "https://googleapis.com/products-api"
                  ],
                  "attributes": {
                    "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-environment": {
                      "stringValues": {
                        "values": [
                          "test-env"
                        ]
                      },
                      "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-environment"
                    },
                    "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-organization": {
                      "stringValues": {
                        "values": [
                          "api-hub-demo-5"
                        ]
                      },
                      "attribute": "projects/api-hub-demo-5/locations/us-central1/attributes/plugin-system-apigee-x-and-hybrid-organization"
                    }
                  }
                },
                "originalId": "apis/Products-API/revisions/1/environments/test-env",
                "originalCreateTime": "2025-07-07T05:23:51.617Z",
                "originalUpdateTime": "2025-07-07T05:23:51.617Z"
              }
            ],
            "originalId": "apis/Products-API/revisions/1",
            "originalCreateTime": "2025-07-07T05:23:47.982Z",
            "originalUpdateTime": "2025-07-07T05:23:47.982Z",
            "specs": [
              {
                "originalModifiedTime": "2025-07-07T05:23:47.982Z",
                "spec": {
                  "displayName": "open-api-spec",
                  "specType": {
                    "enumValues": {
                      "values": [
                        {
                          "id": "openapi"
                        }
                      ]
                    }
                  },
                  "contents": {
                    "mimeType": "application/yaml",
                    "contents": "b3BlbmFwaTogMy4wLjAKaW5mbzoKICB0aXRsZTogUHJvZHVjdHMgQVBJCiAgdmVyc2lvbjogMS4wLjAKICBkZXNjcmlwdGlvbjogQVBJIGZvciBtYW5hZ2luZyBwcm9kdWN0IGludmVudG9yeS4gVGhpcyBBUEkgYWxsb3dzIGZvciB0aGUgY3JlYXRpb24sIHJldHJpZXZhbCwgdXBkYXRlLCBhbmQgZGVsZXRpb24gb2YgcHJvZHVjdCByZWNvcmRzLgpzZXJ2ZXJzOgogIC0gdXJsOiBodHRwczovL2FwaS5leGFtcGxlLmNvbS9wcm9kdWN0cy92MQogICAgZGVzY3JpcHRpb246IFByb2R1Y3Rpb24gc2VydmVyIGZvciB0aGUgUHJvZHVjdHMgQVBJLgpwYXRoczoKICAvcHJvZHVjdHM6CiAgICBnZXQ6CiAgICAgIHN1bW1hcnk6IEdldCBhbGwgcHJvZHVjdHMKICAgICAgb3BlcmF0aW9uSWQ6IGdldEFsbFByb2R1Y3RzCiAgICAgIHJlc3BvbnNlczoKICAgICAgICAnMjAwJzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiBBIGxpc3Qgb2YgcHJvZHVjdHMgc3VjY2Vzc2Z1bGx5IHJldHJpZXZlZC4KICAgICAgICAgIGNvbnRlbnQ6CiAgICAgICAgICAgIGFwcGxpY2F0aW9uL2pzb246CiAgICAgICAgICAgICAgc2NoZW1hOgogICAgICAgICAgICAgICAgdHlwZTogYXJyYXkKICAgICAgICAgICAgICAgIGl0ZW1zOgogICAgICAgICAgICAgICAgICAkcmVmOiAnIy9jb21wb25lbnRzL3NjaGVtYXMvUHJvZHVjdCcKICAgIHBvc3Q6CiAgICAgIHN1bW1hcnk6IEFkZCBhIG5ldyBwcm9kdWN0CiAgICAgIG9wZXJhdGlvbklkOiBhZGRQcm9kdWN0CiAgICAgIHJlcXVlc3RCb2R5OgogICAgICAgIHJlcXVpcmVkOiB0cnVlCiAgICAgICAgZGVzY3JpcHRpb246IFByb2R1Y3QgZGF0YSB0byBiZSBhZGRlZC4KICAgICAgICBjb250ZW50OgogICAgICAgICAgYXBwbGljYXRpb24vanNvbjoKICAgICAgICAgICAgc2NoZW1hOgogICAgICAgICAgICAgICRyZWY6ICcjL2NvbXBvbmVudHMvc2NoZW1hcy9Qcm9kdWN0SW5wdXQnCiAgICAgIHJlc3BvbnNlczoKICAgICAgICAnMjAxJzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiBQcm9kdWN0IHN1Y2Nlc3NmdWxseSBjcmVhdGVkLgogICAgICAgICAgY29udGVudDoKICAgICAgICAgICAgYXBwbGljYXRpb24vanNvbjoKICAgICAgICAgICAgICBzY2hlbWE6CiAgICAgICAgICAgICAgICAkcmVmOiAnIy9jb21wb25lbnRzL3NjaGVtYXMvUHJvZHVjdCcKICAgICAgICAnNDAwJzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiBJbnZhbGlkIGlucHV0IHByb3ZpZGVkIGZvciBwcm9kdWN0IGNyZWF0aW9uLgogIC9wcm9kdWN0cy97cHJvZHVjdElkfToKICAgIGdldDoKICAgICAgc3VtbWFyeTogR2V0IGEgcHJvZHVjdCBieSBJRAogICAgICBvcGVyYXRpb25JZDogZ2V0UHJvZHVjdEJ5SWQKICAgICAgcGFyYW1ldGVyczoKICAgICAgICAtIG5hbWU6IHByb2R1Y3RJZAogICAgICAgICAgaW46IHBhdGgKICAgICAgICAgIHJlcXVpcmVkOiB0cnVlCiAgICAgICAgICBkZXNjcmlwdGlvbjogVW5pcXVlIGlkZW50aWZpZXIgb2YgdGhlIHByb2R1Y3QgdG8gcmV0cmlldmUuCiAgICAgICAgICBzY2hlbWE6CiAgICAgICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgICAgICBmb3JtYXQ6IHV1aWQKICAgICAgcmVzcG9uc2VzOgogICAgICAgICcyMDAnOgogICAgICAgICAgZGVzY3JpcHRpb246IFByb2R1Y3QgZGV0YWlscyBzdWNjZXNzZnVsbHkgcmV0cmlldmVkLgogICAgICAgICAgY29udGVudDoKICAgICAgICAgICAgYXBwbGljYXRpb24vanNvbjoKICAgICAgICAgICAgICBzY2hlbWE6CiAgICAgICAgICAgICAgICAkcmVmOiAnIy9jb21wb25lbnRzL3NjaGVtYXMvUHJvZHVjdCcKICAgICAgICAnNDA0JzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiBQcm9kdWN0IG5vdCBmb3VuZCB3aXRoIHRoZSBnaXZlbiBJRC4KICAgIHB1dDoKICAgICAgc3VtbWFyeTogVXBkYXRlIGFuIGV4aXN0aW5nIHByb2R1Y3QKICAgICAgb3BlcmF0aW9uSWQ6IHVwZGF0ZVByb2R1Y3QKICAgICAgcGFyYW1ldGVyczoKICAgICAgICAtIG5hbWU6IHByb2R1Y3RJZAogICAgICAgICAgaW46IHBhdGgKICAgICAgICAgIHJlcXVpcmVkOiB0cnVlCiAgICAgICAgICBkZXNjcmlwdGlvbjogVW5pcXVlIGlkZW50aWZpZXIgb2YgdGhlIHByb2R1Y3QgdG8gdXBkYXRlLgogICAgICAgICAgc2NoZW1hOgogICAgICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgICAgICAgZm9ybWF0OiB1dWlkCiAgICAgIHJlcXVlc3RCb2R5OgogICAgICAgIHJlcXVpcmVkOiB0cnVlCiAgICAgICAgZGVzY3JpcHRpb246IFVwZGF0ZWQgcHJvZHVjdCBkYXRhLgogICAgICAgIGNvbnRlbnQ6CiAgICAgICAgICBhcHBsaWNhdGlvbi9qc29uOgogICAgICAgICAgICBzY2hlbWE6CiAgICAgICAgICAgICAgJHJlZjogJyMvY29tcG9uZW50cy9zY2hlbWFzL1Byb2R1Y3RJbnB1dCcKICAgICAgcmVzcG9uc2VzOgogICAgICAgICcyMDAnOgogICAgICAgICAgZGVzY3JpcHRpb246IFByb2R1Y3Qgc3VjY2Vzc2Z1bGx5IHVwZGF0ZWQuCiAgICAgICAgJzQwMCc6CiAgICAgICAgICBkZXNjcmlwdGlvbjogSW52YWxpZCBpbnB1dCBwcm92aWRlZCBmb3IgcHJvZHVjdCB1cGRhdGUuCiAgICAgICAgJzQwNCc6CiAgICAgICAgICBkZXNjcmlwdGlvbjogUHJvZHVjdCBub3QgZm91bmQgd2l0aCB0aGUgZ2l2ZW4gSUQuCiAgICBkZWxldGU6CiAgICAgIHN1bW1hcnk6IERlbGV0ZSBhIHByb2R1Y3QKICAgICAgb3BlcmF0aW9uSWQ6IGRlbGV0ZVByb2R1Y3QKICAgICAgcGFyYW1ldGVyczoKICAgICAgICAtIG5hbWU6IHByb2R1Y3RJZAogICAgICAgICAgaW46IHBhdGgKICAgICAgICAgIHJlcXVpcmVkOiB0cnVlCiAgICAgICAgICBkZXNjcmlwdGlvbjogVW5pcXVlIGlkZW50aWZpZXIgb2YgdGhlIHByb2R1Y3QgdG8gZGVsZXRlLgogICAgICAgICAgc2NoZW1hOgogICAgICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgICAgICAgZm9ybWF0OiB1dWlkCiAgICAgIHJlc3BvbnNlczoKICAgICAgICAnMjA0JzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiBQcm9kdWN0IHN1Y2Nlc3NmdWxseSBkZWxldGVkLiBObyBjb250ZW50LgogICAgICAgICc0MDQnOgogICAgICAgICAgZGVzY3JpcHRpb246IFByb2R1Y3Qgbm90IGZvdW5kIHdpdGggdGhlIGdpdmVuIElELgpjb21wb25lbnRzOgogIHNjaGVtYXM6CiAgICBQcm9kdWN0OgogICAgICB0eXBlOiBvYmplY3QKICAgICAgcmVxdWlyZWQ6CiAgICAgICAgLSBpZAogICAgICAgIC0gbmFtZQogICAgICAgIC0gcHJpY2UKICAgICAgcHJvcGVydGllczoKICAgICAgICBpZDoKICAgICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgICAgZm9ybWF0OiB1dWlkCiAgICAgICAgICBkZXNjcmlwdGlvbjogVW5pcXVlIHByb2R1Y3QgaWRlbnRpZmllci4KICAgICAgICBuYW1lOgogICAgICAgICAgdHlwZTogc3RyaW5nCiAgICAgICAgICBkZXNjcmlwdGlvbjogTmFtZSBvZiB0aGUgcHJvZHVjdC4KICAgICAgICBkZXNjcmlwdGlvbjoKICAgICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgICAgbnVsbGFibGU6IHRydWUKICAgICAgICAgIGRlc2NyaXB0aW9uOiBEZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgcHJvZHVjdC4KICAgICAgICBwcmljZToKICAgICAgICAgIHR5cGU6IG51bWJlcgogICAgICAgICAgZm9ybWF0OiBmbG9hdAogICAgICAgICAgZGVzY3JpcHRpb246IFByaWNlIG9mIHRoZSBwcm9kdWN0LgogICAgICAgIHN0b2NrOgogICAgICAgICAgdHlwZTogaW50ZWdlcgogICAgICAgICAgZm9ybWF0OiBpbnQzMgogICAgICAgICAgZGVzY3JpcHRpb246IEN1cnJlbnQgc3RvY2sgcXVhbnRpdHkuCiAgICAgICAgICBkZWZhdWx0OiAwCiAgICBQcm9kdWN0SW5wdXQ6CiAgICAgIHR5cGU6IG9iamVjdAogICAgICByZXF1aXJlZDoKICAgICAgICAtIG5hbWUKICAgICAgICAtIHByaWNlCiAgICAgIHByb3BlcnRpZXM6CiAgICAgICAgbmFtZToKICAgICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgICAgZGVzY3JpcHRpb246IE5hbWUgb2YgdGhlIHByb2R1Y3QuCiAgICAgICAgZGVzY3JpcHRpb246CiAgICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgICAgIG51bGxhYmxlOiB0cnVlCiAgICAgICAgICBkZXNjcmlwdGlvbjogRGV0YWlsZWQgZGVzY3JpcHRpb24gb2YgdGhlIHByb2R1Y3QuCiAgICAgICAgcHJpY2U6CiAgICAgICAgICB0eXBlOiBudW1iZXIKICAgICAgICAgIGZvcm1hdDogZmxvYXQKICAgICAgICAgIGRlc2NyaXB0aW9uOiBQcmljZSBvZiB0aGUgcHJvZHVjdC4KICAgICAgICBzdG9jazoKICAgICAgICAgIHR5cGU6IGludGVnZXIKICAgICAgICAgIGZvcm1hdDogaW50MzIKICAgICAgICAgIGRlc2NyaXB0aW9uOiBJbml0aWFsIHN0b2NrIHF1YW50aXR5LgogICAgICAgICAgZGVmYXVsdDogMA=="
                  }
                }
              }
            ]
          }
        ],
        "originalId": "apis/Products-API",
        "originalUpdateTime": "2025-07-07T05:23:47.982Z"
      }
    ]
  }
}

Now that you have successfully tested the integration, lets publish it to make it active and available for use in API hub. To publish the integration, click Publish in the integration editor toolbar.

Upon successfully publishing your integration, you can view and inspect the execution logs of the published integration. To view logs, click View execution logs for this integration.

Step 2: Set up custom curation in API hub

Now that your integration is published, it's time to connect your custom curation logic to API hub.

  1. In the Google Cloud console, go to the API hub page.

    Go to API hub

  2. Click Settings from the left navigation menu to open the Settings page.
  3. Go to the Curations tab and click Set up a new curation.
  4. In the Set up curation pane, provide the following details:

    1. Display name: enter enrich-curation.
    2. Description: enter Custom curation to enrich API using spec files in GCS bucket.
    3. Under Associate an existing integration section:
      1. Integration: choose enrich-with-spec-yaml.
      2. Trigger ID: Choose api_trigger/test-custom-curation_API_1.
    4. Click Create curation to initiate the creation of the custom curation.

    A Curation created successfully notification appears when complete.

Step 3: Edit existing plugin instance

API hub creates a default plugin instance for Apigee and Apigee hybrid upon provisioning. To apply your new logic, you will now edit this instance to use the custom curation you just created.

To edit the curation settings of the existing Apigee and hybrid plugin instance, do the following:

  1. In the Google Cloud console, go to the API hub page.

    Go to API hub

  2. Click Settings from the left navigation menu to open the Settings page.
  3. Click the Plugins tab and go to the Manage instances section to view the list of plugin instances in your project.
  4. Find the Apigee X and Hybrid plugin instance, click (Plugin instance actions) and then select See details.
  5. In the Plugin details pane, modify the Curation logic to Custom curation and choose enrich-with-spec-yaml as the curation logic.
  6. Click Save to apply your changes.

Step 4: Verify enriched API data

As the final step, lets verify that the custom curation is working as expected by checking API hub to see the newly enriched API data.

To verify the enriched data, do the following:

  1. In the Google Cloud console, go to the APIs page in API hub.

    Go to API hub

    A list of all the available APIs are listed on the APIs home page.

  2. Use Filter to filter the following list of APIs:

    1. Orders API
    2. Products API
    3. Users API
  3. Click an API to view the API details page. Verify the API details and check if it is enriched with the specification files from the Cloud Storage bucket.

The following image shows a sample products API with the enriched details: Enriched API details

Clean up

To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, either delete the project that contains the resources, or keep the project and delete the individual resources.

Delete the project

    Delete a Google Cloud project:

    gcloud projects delete PROJECT_ID

Delete individual resources

  1. Delete the bucket:
    gcloud storage buckets delete BUCKET_NAME