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:
- An Ubuntu server with the Anypoint CLI 4.x installed and configured. Refer to How to start using Anypoint CLI from your CI/CD post if the server is not yet set up.
- The
GON HTTPS Enforcementruleset already published to Exchange. Refer to our post Custom Governance Rulesets with the Anypoint CLI - Environment variables exported in the current shell:
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
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 |
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.
./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:uploadwith theoas.yamlclassifier,mainFileproperty, andrest-apitype. - 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.





