API Governance as a Quality Gate: Validate Before You Publish


Governance without enforcement is just documentation. We can write standards, publish rulesets, and build profiles in Anypoint Platform — but if we publish APIs to Exchange before they pass those standards, governance is a report, not a gate.

In this post, we'll change that. We'll validate an OAS API spec against our custom HTTPS ruleset before it reaches Exchange. Check out our previous post to see how to create that custom ruleset. If it fails, the process stops. If it passes, the spec publishes automatically. We'll then wrap the entire workflow into a reusable shell script that any team can drop into a pipeline.


Prerequisites

We'll need the following before we start:

export ANYPOINT_CLIENT_ID=myClientID
export ANYPOINT_CLIENT_SECRET=myClientSecret
export ANYPOINT_ORG=myOrgId


We'll verify authentication is active before we proceed:

anypoint-cli-v4 account:environment:list


Step 1 — Set Up the Project Folder

We'll create a working directory for our API spec files:

mkdir -p ~/governance/gon-order-api
cd ~/governance/gon-order-api

Step 2 — Create the API Spec (HTTP — Intentionally Wrong)

We'll start with an API spec that uses HTTP. This will trigger a violation in our ruleset. We do this on purpose — we need to see the gate fail before we see it pass.

We'll create the spec file:

vi order-api.yaml


We'll paste the following OAS 3.0 spec:

openapi: "3.0.0"
info:
  title: GON Order API
  description: Manages customer orders for GON services
  version: "1.0.0"
servers:
  - url: http://api.gon.com/orders/v1
    description: Production server
paths:
  /orders:
    get:
      summary: List all orders
      operationId: listOrders
      responses:
        "200":
          description: A list of orders
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    orderId:
                      type: string
                    status:
                      type: string

We'll save the file with Ctrl+X, then Y, then Enter.

The violation is on line 7: url: http://api.gon.com/orders/v1. Our ruleset requires the URL to start with https://. This spec deliberately uses http://.


Step 3 — Add the exchange.json File

Before we can validate, the CLI requires an exchange.json file at the top level of the project folder. This file identifies the project to the CLI — it declares the asset type, the main spec file, and the organization it belongs to.

Without it, the CLI returns: Error: exchange.json file must be present at top level of the project zip or folder.

We'll create it:

nano ~/governance/gon-order-api/exchange.json


We'll paste the following, replacing YOUR_ORG_ID with our Anypoint organization ID:

{
  "classifier": "oas",
  "assetId": "gon-order-api",
  "groupId": "YOUR_ORG_ID",
  "version": "1.0.0",
  "name": "GON Order API",
  "main": "order-api.yaml",
  "type": "rest-api"
}

We can retrieve our organization ID from the environment variable we already have set:

echo $ANYPOINT_ORG


What each field does:

Field Value Purpose
classifier oas Declares the spec format as OpenAPI Specification
assetId gon-order-api The asset ID that will appear in Exchange
groupId our org ID The organization that owns this asset in Exchange
version 1.0.0 The asset version
main order-api.yaml The entry point file for the API project
type rest-api The Exchange asset type


Our project folder now contains:

gon-order-api/
├── exchange.json
└── order-api.yaml


Step 4 — Validate the Spec Against the Ruleset (Failing Case)

The governance:api:validate command validates an API spec against one or more rulesets. We'll use the --rulesets flag to point it at our local ruleset file.

The command accepts an API project as a folder or a ZIP file — not a raw YAML file directly. We'll pass the project folder:

anypoint-cli-v4 governance:api:validate \
  ~/governance/gon-order-api \
  --rulesets ~/governance/gon-https-ruleset/ruleset.yaml


Expected output:

Conforms: false
Number of results: 1

Conformance Validations (1)
-----------------------

Constraint: file:///home/ubuntu/governance/gon-https-ruleset/ruleset.yaml#/encodes/validations/use-https-for-urls
Severity: Violation
Message: Always use https for URLs
Target: amf://id#3
Range: [(7,4)-(8,47)]
Location: file:///home/ubuntu/governance/gon-order-api/order-api.yaml


The output tells us exactly what failed and where:

Field What It Tells Us
Conforms: false The spec did not pass the governance check
Severity: Violation This is a hard failure, not a warning
Message: Always use https for URLs The message we defined in our ruleset
Range: [(7,4)-(8,47)] Lines 7–8 in the spec file — the servers block
Location The exact file that failed

The gate is working. The spec fails, and we do not publish it.


Step 5 — Fix the API Spec (HTTPS — Correct)

We'll open the spec and update the server URL from http:// to https://:

nano ~/governance/gon-order-api/order-api.yaml


We'll change line 7 from:

  - url: http://api.gon.com/orders/v1


To:

  - url: https://api.gon.com/orders/v1


We'll save and close the file.


Step 6 — Validate the Spec Again (Passing Case)

We'll re-run the same validation command:

anypoint-cli-v4 governance:api:validate \
  ~/governance/gon-order-api \
  --rulesets ~/governance/gon-https-ruleset/ruleset.yaml


Expected output:

Spec conforms with Ruleset



The spec passes. The gate is clear. We can now publish.


Step 7 — Publish the API Spec to Exchange

We'll use exchange:asset:upload to publish the passing API spec to Anypoint Exchange as an OAS REST API asset.

We'll run:

anypoint-cli-v4 exchange:asset:upload gon-order-api/1.0.0 \
  --name "GON Order API" \
  --description "Manages customer orders for GON services. Validated against GON HTTPS Enforcement ruleset." \
  --keywords orders,rest-api,gon \
  --tags rest-api \
  --type rest-api \
  --properties='{"mainFile":"order-api.yaml","apiVersion":"v1"}' \
  --files='{"oas.yaml":"'$HOME'/governance/gon-order-api/order-api.yaml"}'


Expected output:

Asset gon-order-api/1.0.0 uploaded successfully


Breaking down the key flags:

Flag Value Purpose
gon-order-api/1.0.0 Asset identifier Sets the asset ID and version in Exchange
--type rest-api Tells Exchange this is an OAS/REST API spec
--properties mainFile + apiVersion Declares the entry point file and API version
--files oas.yaml classifier The oas.yaml key tells Exchange this file is an OAS specification


We'll verify the publication:

anypoint-cli-v4 exchange:asset:list gon-order-api

Expected output:

Name           AssetId        Version  Type
─────────────  ─────────────  ───────  ────────
GON Order API  gon-order-api  1.0.0    rest-api


The API spec is live in Exchange and confirmed to comply with our HTTPS standard.


Step 8 — The Quality Gate Script

We've proven the workflow manually. Now we'll automate it.

The script below does four things: validates the spec, stops with a non-zero exit code if it fails, publishes to Exchange if it passes, and outputs a clear status message at each stage. That exit code behavior is what makes this usable as a CI/CD gate — pipeline runners treat any non-zero exit as a build failure.

We'll create the script:

nano ~/governance/publish-api.sh

We'll paste the following:

#!/bin/bash
set -e

# ─────────────────────────────────────────────────────────────
# GON API Quality Gate and Publisher
# Usage: ./publish-api.sh <api-spec-file> <asset-id> <version> <api-name>
# Example: ./publish-api.sh order-api.yaml gon-order-api 1.0.0 "GON Order API"
# ─────────────────────────────────────────────────────────────

# --- Arguments ---
API_SPEC="$1"
ASSET_ID="$2"
VERSION="$3"
API_NAME="$4"

# --- Configuration ---
RULESET=~/governance/gon-https-ruleset/ruleset.yaml

# --- Validate inputs ---
if [ -z "$API_SPEC" ] || [ -z "$ASSET_ID" ] || [ -z "$VERSION" ] || [ -z "$API_NAME" ]; then
  echo "ERROR: Missing required arguments."
  echo "Usage: $0 <api-spec-file> <asset-id> <version> <api-name>"
  exit 1
fi

if [ ! -f "$API_SPEC" ]; then
  echo "ERROR: API spec file not found: $API_SPEC"
  exit 1
fi

if [ ! -f "$RULESET" ]; then
  echo "ERROR: Ruleset file not found: $RULESET"
  exit 1
fi

# --- Required environment variables ---
if [ -z "$ANYPOINT_CLIENT_ID" ] || [ -z "$ANYPOINT_CLIENT_SECRET" ] || [ -z "$ANYPOINT_ORG" ]; then
  echo "ERROR: ANYPOINT_CLIENT_ID, ANYPOINT_CLIENT_SECRET, and ANYPOINT_ORG must be set."
  exit 1
fi

# --- Required system tools ---
if ! command -v zip &> /dev/null; then
  echo "ERROR: 'zip' is not installed. Run: sudo apt-get install -y zip"
  exit 1
fi

# --- Prepare ZIP for validation ---
# governance:api:validate requires a ZIP or folder with an exchange.json file.
# We generate the exchange.json from our arguments and zip everything together.
SPEC_DIR=$(dirname "$API_SPEC")
SPEC_FILE=$(basename "$API_SPEC")
SPEC_ZIP="${SPEC_DIR}/${SPEC_FILE%.yaml}.zip"
EXCHANGE_JSON="${SPEC_DIR}/exchange.json"

cat > "$EXCHANGE_JSON" <<EOF
{
  "classifier": "oas",
  "assetId": "${ASSET_ID}",
  "groupId": "${ANYPOINT_ORG}",
  "version": "${VERSION}",
  "name": "${API_NAME}",
  "main": "${SPEC_FILE}",
  "type": "rest-api"
}
EOF

cd "$SPEC_DIR"
zip -j "$SPEC_ZIP" "$SPEC_FILE" exchange.json
cd - > /dev/null

# ─────────────────────────────────────────────────────────────
# STEP 1 — Governance Validation
# ─────────────────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  GON API Quality Gate"
echo "  Spec   : $API_SPEC"
echo "  Asset  : $ASSET_ID/$VERSION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "[1/2] Running governance validation..."

VALIDATION_OUTPUT=$(anypoint-cli-v4 governance:api:validate "$SPEC_ZIP" \
  --rulesets "$RULESET" 2>&1)

echo "$VALIDATION_OUTPUT"

if echo "$VALIDATION_OUTPUT" | grep -q "Spec does not conform with Ruleset"; then
  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "  GOVERNANCE FAILED — Publication blocked."
  echo "  Fix all violations before publishing."
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  rm -f "$SPEC_ZIP" "$EXCHANGE_JSON"
  exit 1
fi

echo ""
echo "  ✓ Governance passed."
rm -f "$SPEC_ZIP" "$EXCHANGE_JSON"

# ─────────────────────────────────────────────────────────────
# STEP 2 — Publish to Exchange
# ─────────────────────────────────────────────────────────────
echo ""
echo "[2/2] Publishing to Exchange..."

anypoint-cli-v4 exchange:asset:upload "${ASSET_ID}/${VERSION}" \
  --name "$API_NAME" \
  --description "Published via GON quality gate. Validated against GON HTTPS Enforcement ruleset." \
  --type rest-api \
  --properties="{\"mainFile\":\"$(basename "$API_SPEC")\",\"apiVersion\":\"v1\"}" \
  --files="{\"oas.yaml\":\"$API_SPEC\"}"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  ✓ Published: $API_NAME ($ASSET_ID/$VERSION)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

We'll save and close the file, then make it executable:

chmod +x ~/governance/publish-api.sh


Step 9 — Run the Script

Failing case — spec with HTTP:

./publish-api.sh \
  ~/governance/gon-order-api/order-api-http.yaml \
  gon-order-api \
  1.0.0 \
  "GON Order API"


Expected output:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  GON API Quality Gate
  Spec   : /home/ubuntu/governance/gon-order-api/order-api-http.yaml
  Asset  : gon-order-api/1.0.0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/2] Running governance validation...
Conforms: false
...
Message: Always use https for URLs

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  GOVERNANCE FAILED — Publication blocked.
  Fix all violations before publishing.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


The script exits with code 1. In a CI/CD pipeline, this fails the build stage.



Passing case — spec with HTTPS:

./publish-api.sh \
  ~/governance/gon-order-api/order-api.yaml \
  gon-order-api \
  1.0.0 \
  "GON Order API"


Expected output:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  GON API Quality Gate
  Spec   : /home/ubuntu/governance/gon-order-api/order-api.yaml
  Asset  : gon-order-api/1.0.0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/2] Running governance validation...
Spec conforms with Ruleset

  ✓ Governance passed.

[2/2] Publishing to Exchange...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ✓ Published: GON Order API (gon-order-api/1.0.0)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


The script exits with code 0. The pipeline stage passes and the API is live in Exchange.


How to Use the Script in a CI/CD Pipeline

The script accepts four arguments. We pass them from our pipeline configuration:

./publish-api.sh <api-spec-file> <asset-id> <version> <api-name>


A GitHub Actions step looks like this:

- name: Validate and Publish API to Exchange
  env:
    ANYPOINT_CLIENT_ID: ${{ secrets.ANYPOINT_CLIENT_ID }}
    ANYPOINT_CLIENT_SECRET: ${{ secrets.ANYPOINT_CLIENT_SECRET }}
    ANYPOINT_ORG: ${{ secrets.ANYPOINT_ORG }}
  run: |
    ./governance/publish-api.sh \
      ./apis/order-api.yaml \
      gon-order-api \
      ${{ github.ref_name }} \
      "GON Order API"


A Jenkins pipeline step looks like this:

stage('Validate and Publish API') {
    environment {
        ANYPOINT_CLIENT_ID     = credentials('anypoint-client-id')
        ANYPOINT_CLIENT_SECRET = credentials('anypoint-client-secret')
        ANYPOINT_ORG           = credentials('anypoint-org-id')
    }
    steps {
        sh './governance/publish-api.sh ./apis/order-api.yaml gon-order-api 1.0.0 "GON Order API"'
    }
}


In both cases, the pipeline injects credentials as environment variables. The script reads them automatically. No credentials ever appear in the script file or in source control.


Summary

We built a complete API quality gate from scratch:

  • We created an OAS spec with an HTTP URL, ran governance validation against our custom ruleset, and observed the violation output — including the exact file location and line range.
  • We fixed the spec to use HTTPS, re-ran validation, and confirmed it passed.
  • We published the validated spec to Exchange using exchange:asset:upload with the oas.yaml classifier, mainFile property, and rest-api type.
  • We wrapped the entire workflow into a reusable shell script that takes the spec file, asset ID, version, and name as arguments, validates first, blocks publication on failure with a non-zero exit code, and publishes on success.

This script is a drop-in quality gate for any CI/CD pipeline. It validates before it publishes. It never lets a non-conformant spec reach Exchange.

In the next post, we'll extend this gate further. We'll validate our API specs against the ruleset published to Exchange using the --remote-rulesets flag — so the gate always uses the latest approved version of our standards without us updating the script.

Previous Post Next Post