Generally, policies work the same for API products that you expose in the developer portal as described in the following docs:

Sometimes, the way you configure routing and policies differs when your API products are exposed with Portal. Portal provides extra flexibility to describe your API products that subsequent policies can take advantage of. Refer to the following guides for these special use cases:

  • Usage plans that bundle rate limiting and external auth policies into flexible plans that you can use to protect and monetize your API products
  • Path rewrites so you can decouple your app development from the paths of your API product exposed in the developer portal
  • Custom metadata from your API products that you can add to requests and responses by using a header manipulation policy

Add custom metadata from API products to requests and responses

You can set custom metadata for your API products when you bundle your APIs in a route table. Then, the custom metadata is available for your developer portal and analytics. You can also add the custom metadata in subsequent requests and responses to your APIs by using a header manipulation policy.

  1. Install or upgrade Portal. In the istioInstallations section of the gloo-platform Helm release, enable the following access logs for the ingress gateway. The Monitor Portal analytics guide includes an example of enabling access logs in the ingress gateway. For more information about access logs, see Access logs.

    • The user_id, api_id, api_product_id, api_product_name, usage_plan, and custom_metadata come from the Portal metadata that you configure later in the route table for your API products.
    • The "x_tenant_id": "%REQ(X-TENANT-ID)%" is a request header that you append later in the header manipulation policy.
        enabled: true
          - istioOperatorSpec:
                # Enable access logging to /dev/stdout
                accessLogFile: /dev/stdout
                # Encoding for the access log (TEXT or JSON). Default value is TEXT.
                accessLogEncoding: JSON
                # If empty, the default log format is used.
                # See the default log format at https://istio.io/latest/docs/tasks/observability/logs/access-log/#default-access-log-format
                # To change the format, see https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-rules
                accessLogFormat: |
                    "timestamp": "%START_TIME%",
                    "server_name": "%REQ(:AUTHORITY)%",
                    "response_duration": "%DURATION%",
                    "request_command": "%REQ(:METHOD)%",
                    "request_uri": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%",
                    "request_protocol": "%PROTOCOL%",
                    "status_code": "%RESPONSE_CODE%",
                    "client_address": "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%",
                    "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%",
                    "bytes_sent": "%BYTES_SENT%",
                    "bytes_received": "%BYTES_RECEIVED%",
                    "user_agent": "%REQ(USER-AGENT)%",
                    "downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%",
                    "requested_server_name": "%REQUESTED_SERVER_NAME%",
                    "request_id": "%REQ(X-REQUEST-ID)%",
                    "response_flags": "%RESPONSE_FLAGS%",
                    "route_name": "%ROUTE_NAME%",
                    "upstream_cluster": "%UPSTREAM_CLUSTER%",
                    "upstream_host": "%UPSTREAM_HOST%",
                    "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%",
                    "upstream_service_time": "%REQ(x-envoy-upstream-service-time)%",
                    "upstream_transport_failure_reason": "%UPSTREAM_TRANSPORT_FAILURE_REASON%",
                    "correlation_id": "%REQ(X-CORRELATION-ID)%",
                    "user_id": "%DYNAMIC_METADATA(envoy.filters.http.ext_authz:userId)%",
                    "api_id": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_id)%",
                    "api_product_id": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_product_id)%",
                    "api_product_name": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_product_name)%",
                    "usage_plan": "%DYNAMIC_METADATA(envoy.filters.http.ext_authz:usagePlan)%",
                    "custom_metadata": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata)%",
                    "x_tenant_id": "%REQ(X-TENANT-ID)%"
            revision: auto   
  2. Create your APIs.

  3. Bundle your APIs into API products by creating a route table. In the portalMetadata section of the route table, make sure to your custom metadata, such as compatability and tenant in the following example.

        apiProductId: "tracks"
        apiProductDisplayName: "Catstronauts Course Tracks"
        apiVersion: "v1"
        title: "Catstronauts REST API"
        description: "REST API for Catstronauts to retrieve data for tracks, authors and modules."
        termsOfService: "You must authenticate to use this API! And other Terms of Service."
        contact: "support@example.com"
        license: "License info, such as MIT"
        lifecycle: "Supported"
          compatibility: "None"
          tenantId: "tenant-us-east2" 
  4. Apply a header manipulation policy that appends the custom metadata from the API product to the request header. The policy gets the custom metadata from the %DYNAMIC_METADATA% in the access logs of the ingress gateway. For more information and other options such as removing request or response headers, see the Header manipulation guide or API reference docs. In the following example, the policy:

    • Appends the tenantId as a request header so that subsequent processing can take place based on the tenant ID.
    • Appends the compatibility as a response header so that the client can tell whether the API product is backwards compatible.
      kubectl apply -f - << EOF
    apiVersion: trafficcontrol.policy.gloo.solo.io/v2
    kind: HeaderManipulationPolicy
      name: portal-custom-metadata
      namespace: tracks
      - route:
            api: tracks
          x-tenant-id: "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata:tenantId)%"
          x-compatibility: "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata:compatibility)%"
  5. Get the external address of your ingress gateway. The steps vary depending on the type of load balancer that backs the ingress gateway.

    • LoadBalancer IP address:
        export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
      echo $INGRESS_GW_IP
    • LoadBalancer hostname:
        export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
      echo $INGRESS_GW_IP

    Note: Depending on your environment, you might see <pending> instead of an external IP address. For example, if you are testing locally in kind or minikube, or if you have insufficent permissions in your cloud platform, you can instead port-forward the service port of the ingress gateway:

      kubectl -n gloo-mesh-gateways port-forward deploy/istio-ingressgateway-1-20 8081
  6. Send a request to the Tracks app. Note that if you already included the Tracks app in a usage plan, you might need to authenticate such as with an API key.

      curl -vik http://$INGRESS_GW_ADDRESS:80/trackapi/tracks -H "host: api.example.com:80"

    In the response, note that the x-compatibility response header is returned.

      x-compatibility: None
  7. Check the ingress gateway logs for the x_tenant_id request header that you appended to requests sent to the upstream service.

      kubectl logs -n gloo-mesh-gateways -l app=istio-ingressgateway

    Example output:

      "api_product_id": "tracks",
      "request_command": "GET",
      "api_product_name": "Catstronauts Course",
      "user_agent": "curl/8.1.2",
      "server_name": "api.example.com",
      "custom_metadata": {
        "tenantId": "tenant-us-east2",
        "compatibility": "None"
      "response_duration": 11,
      "x_forwarded_for": "",
      "status_code": 200,
      "usage_plan": "gold",
      "user_id": "user1",
      "api_id": "tracks",
      "request_uri": "/trackapi/tracks",
      "x_tenant_id": "tenant-us-east2"


You can optionally remove the resources that you set up as part of this guide.
  kubectl delete HeaderManipulationPolicy portal-custom-metadata -n tracks