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'scustom 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 thepricing calculator.

New Google Cloud users might be eligible for afree 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, seeClean 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 nameConfiguration details
Base configRevision 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
Note: Ensure that each proxy has at least one revision deployed to an environment, and the environment must be part of an environment group.

For more information about how to register a proxy in Apigee, seeCreate 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:

Note: For the purpose of this tutorial, you are only required to set theGlobal unique name field for your bucket; all other steps are either optional or can have the default settings.
  1. In the Google Cloud console, go to the Cloud StorageBuckets page.

    Go to Buckets

  2. ClickCreate.
  3. On theCreate a bucket page, entercuration_bucket as the bucket name.
  4. ClickCreate.

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

  5. ClickConfirm.

    A new Cloud Storage bucket namedcuration_bucket is created and is listed in theBuckets 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 fileDescriptionDownload link
orders-api.yamlThis specification defines an API for managing customer orders.Click to download
products-api.yamlThis specification describes an API for managing products.Click to download
users-api.yamlThis 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 StorageBuckets page.

    Go to Buckets

  2. Click thecuration_bucket bucket that you created to open theBucket details page.
  3. In theObjects tab, clickUpload > 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 theIntegration Connectors page.

    Go to Integration Connectors

  2. ClickConnections from the left navigation menu to open theConnections page.
  3. ClickCreate New and provide the following details in theCreate Connection page:

    1. Location: select a desired location and clickNext.
    2. Connector: selectCloud Storage from the drop-down list and clickNext.
    3. Connector version: enter the version of your connector.
    4. Connection name: enterfetch-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, seeFind the project name, number, and ID.
    Note: Integration Connectors automatically checks if your default service account associated with the selected project ID has the required IAM roles. If the verification fails, clickGRANT to manually grant this access.1. ClickNext.
  4. Review your connection details and clickCreate.A new connection is created and is listed in theConnections 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 fileDownload link
enrich-with-spec-yaml.jsonClick 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 theApplication Integration page.

    Go to Application Integration

  2. ClickIntegrations from the left navigation menu to open theIntegrations page.
  3. ClickCreate integration and provide the following details in theCreate Integration page:
    1. Integration name: entertest-curation.
    2. Description: enterDemo integration created for custom curation tutorial.
    3. Region: select a desired location .
    4. ClickCreate. The new integration opens in the integration editor.
  4. In the designer toolbar, click (Actions menu) and selectUpload.
  5. In the file browser dialog, select the downloaded JSON file, and then clickOpen.

    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 theConnectors task namedFetch spec from GCS to view the connector configuration pane.
  2. ClickConfigure Connector.
  3. Configure the following details in theConnectors Task Editor page:
    1. Region: select a desired location .
    2. Connection: Selectfetch-specs.
    3. Type: SelectActions.
    4. Set entities/actions: SelectDownloadObject as the action.
    5. ClickDone.

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:

ComponentConfigurationDescription
API triggerAPI Trigger ID:api_trigger/test-custom-curation_API_1Receives 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 theapiMetadata list and calls the sub-integration for each API resource. It also collates the response of each run incuratedAPIMetadataList, 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_1Invokes the sub-integration for each element inapiMetaData.
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 forConnectorInputPayload.
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$ = 0Checks for successful connection execution and payload retrieval. If yes, proceeds to parse the spec contents.
Edge condition

(Failed to fetch spec)

Condition:$`ErrorInfo`.code$ != 0Checks theconnectorOutputPayload 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 theconnectorOutputPayload 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 parsedspecContent output from the Data Mapping task. The function constructs an updatedcuratedAPIMetadata object. It structures the provided specification content into a new spec object, identifies it as an OpenAPI YAML, and embeds it into aspecMetadata object along with its modification timestamp. ThisspecMetadata is then integrated into the specs array of the API's first version withincuratedAPIMetadata, effectively linking the API proxy with its detailed specification. Finally, the enrichedcuratedAPIMetadata 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 curatedapiData 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, clickTest 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, clickPublish in the integration editor toolbar.

Upon successfully publishing your integration, you canview and inspect the execution logs of the published integration. To view logs, clickView 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 theAPI hub page.

    Go to API hub

  2. ClickSettings from the left navigation menu to open theSettings page.
  3. Go to theCurations tab and clickSet up a new curation.
  4. In theSet up curation pane, provide the following details:

    1. Display name: enterenrich-curation.
    2. Description: enterCustom curation to enrich API using spec files in GCS bucket.
    3. UnderAssociate an existing integration section:
      1. Integration: chooseenrich-with-spec-yaml.
      2. Trigger ID: Chooseapi_trigger/test-custom-curation_API_1.
    4. ClickCreate curation to initiate the creation of the custom curation.

    ACuration 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 theAPI hub page.

    Go to API hub

  2. ClickSettings from the left navigation menu to open theSettings page.
  3. Click thePlugins tab and go to theManage instances section to view the list of plugin instances in your project.
  4. Find theApigee X and Hybrid plugin instance, click (Plugin instance actions) and then selectSee details.
  5. In thePlugin details pane, modify theCuration logic toCustom curation and chooseenrich-with-spec-yaml as the curation logic.
  6. ClickSave 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 theAPIs page inAPI hub.

    Go to API hub

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

  2. UseFilter to filter the following list of APIs:

    1. Orders API
    2. Products API
    3. Users API
  3. Click an API to view theAPI 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

    Caution: Deleting a project has the following effects:
    • Everything in the project is deleted. If you used an existing project for the tasks in this document, when you delete it, you also delete any other work you've done in the project.
    • Custom project IDs are lost. When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as anappspot.com URL, delete selected resources inside the project instead of deleting the whole project.

    If you plan to explore multiple architectures, tutorials, or quickstarts, reusing projects can help you avoid exceeding project quota limits.

    Delete a Google Cloud project:

    gcloud projects deletePROJECT_ID

Delete individual resources

  1. Delete the bucket:
    gcloud storage buckets deleteBUCKET_NAME
    Important: Your bucket must be empty before you can delete it.

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-19 UTC.