Integration
How to integrate PillarShield's content governance API into custom applications, enterprise CMS platforms (Adobe AEM, Sitecore, Contentful), or CI/CD pipelines.
Overview
PillarShield provides a synchronous REST API that evaluates content against your tenant's governance rules — PII detection, prohibited terms, tone analysis, and moderation — and returns a compliance decision. It sits at the save or publish boundary of your content lifecycle.
Key Capabilities
- Real-time compliance: ~1.2 seconds for a full governance check including AI analysis.
- Deterministic rules: Regex-based PII, credential, and prohibited term blocking. Same input, same result, every time.
- AI checks: Tone scoring, PII classification, and OpenAI-powered moderation. Warns below your certainty threshold, blocks above it.
- Audit trail: Every check is logged server-side as immutable evidence.
Quick Start
Base URL: https://api.pillarshield.co/pillarshield-governance
Authentication: API key (psk_ prefix) in the JSON request body. Not an HTTP header.
API Reference: pillarshield.co/docs/api (OpenAPI spec)
Security: Never expose your api_key in client-side code. Always call the API from your backend or CI environment.
Minimal Example (curl)
The simplest possible governance check. Two required fields: api_key and content.
curl -X POST https://api.pillarshield.co/pillarshield-governance/governance \
-H "Content-Type: application/json" \
-d '{
"api_key": "psk_YOUR_KEY",
"content": "Call John at 555-867-5309 to discuss the product launch."
}'
Response:
{
"status": "VIOLATION_DETECTED",
"is_compliant": false,
"reason": ["PII detected: phone number"],
"violations": [
{
"type": "PII_PHONE",
"severity": "error",
"effective_severity": "error",
"message": "PII detected: phone number",
"location": { "start": 13, "end": 25 },
"match": {
"kind": "phone",
"value": "555-***-****",
"masked": true
}
}
],
"request_id": "4f1f0b77-8c5c-4b2a-9b25-0b3a2f5f2f33"
}
That's it. Check is_compliant. If false, the violations array tells you what was found and where.
Integration Patterns
1. Pre-Save Hook (Recommended)
Block non-compliant content before it reaches your database. This is what the Drupal and WordPress connectors do natively.
// Node.js — Express middleware example
const axios = require('axios');
const PILLARSHIELD_URL = 'https://api.pillarshield.co/pillarshield-governance/governance';
async function governanceCheck(content, pageContext) {
const { data } = await axios.post(PILLARSHIELD_URL, {
api_key: process.env.PILLARSHIELD_API_KEY,
content: content,
context: {
page_type: pageContext.type,
page_title: pageContext.title,
content_context: 'ui:publish'
}
});
return data;
}
// In your save handler:
app.post('/api/content', async (req, res) => {
const result = await governanceCheck(req.body.content, {
type: 'blog',
title: req.body.title
});
if (!result.is_compliant) {
return res.status(422).json({
error: 'Content governance violation',
violations: result.violations.map(v => v.message)
});
}
await db.save(req.body);
res.json({ saved: true });
});
2. Python (Django/Flask)
Same pattern, different language.
import os
import requests
PILLARSHIELD_URL = "https://api.pillarshield.co/pillarshield-governance/governance"
def check_content(content, page_type="page", page_title=""):
response = requests.post(PILLARSHIELD_URL, json={
"api_key": os.environ["PILLARSHIELD_API_KEY"],
"content": content,
"context": {
"page_type": page_type,
"page_title": page_title,
"content_context": "ui:publish"
}
})
return response.json()
# Usage:
result = check_content(draft_body, page_type="blog", page_title="New Post")
if not result["is_compliant"]:
errors = [v["message"] for v in result["violations"]]
raise ValidationError(f"Blocked: {', '.join(errors)}")
3. CI/CD Pipeline
For teams managing content as code (Markdown docs, static site generators), enforce governance in your build pipeline.
# GitHub Actions example
name: Content Governance
on: [pull_request]
jobs:
governance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changed content files
env:
PILLARSHIELD_API_KEY: ${{ secrets.PILLARSHIELD_API_KEY }}
run: |
# Find changed .md files in this PR
FILES=$(git diff --name-only origin/main -- '*.md')
FAILED=0
for f in $FILES; do
echo "Checking: $f"
CONTENT=$(cat "$f")
RESPONSE=$(curl -s -X POST \
https://api.pillarshield.co/pillarshield-governance/governance \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg key "$PILLARSHIELD_API_KEY" \
--arg content "$CONTENT" \
--arg title "$(basename "$f" .md)" \
'{api_key: $key, content: $content, context: {page_type: "markdown", page_title: $title, content_context: "ui:publish"}}')")
COMPLIANT=$(echo "$RESPONSE" | jq -r '.is_compliant')
if [ "$COMPLIANT" = "false" ]; then
echo "❌ BLOCKED: $f"
echo "$RESPONSE" | jq '.violations[] | .message'
FAILED=1
else
echo "✅ Passed: $f"
fi
done
if [ "$FAILED" -eq 1 ]; then
echo "::error::Content governance violations found. Fix and re-push."
exit 1
fi
Deep Dive: Git Pre-Push Hook
Some content teams publish directly from Git — merging to main triggers a deploy. If that's your workflow, you can enforce governance with a Git pre-push hook that blocks the push if any changed content files have violations.
This is a local hook. It runs on the developer's machine before the push reaches the remote. Nothing ships if content doesn't pass.
How it works
- Developer finishes editing content files (
.md,.html,.json) - Developer runs
git push - The pre-push hook identifies changed content files vs. the remote
- Each file is sent to
POST /governance - If any file fails: push is blocked, violations are printed
- If all pass: push proceeds normally
Install
Save the script below as .git/hooks/pre-push and make it executable (chmod +x .git/hooks/pre-push). For team-wide enforcement, commit the hook to your repo and have developers symlink it, or use a tool like Lefthook or Husky to manage hooks automatically.
The hook
#!/usr/bin/env bash
#
# .git/hooks/pre-push
# PillarShield governance check on changed content files before push.
#
# Requirements: curl, jq
# Environment: PILLARSHIELD_API_KEY must be set
#
# Content file patterns to check (edit to match your repo):
CONTENT_PATTERNS="*.md *.html *.json"
set -euo pipefail
API_URL="https://api.pillarshield.co/pillarshield-governance/governance"
if [ -z "${PILLARSHIELD_API_KEY:-}" ]; then
echo "⚠️ PILLARSHIELD_API_KEY not set. Skipping governance check."
echo " Set it in your shell profile or .env to enable pre-push checks."
exit 0
fi
# Read the remote and URL that git is pushing to
read -r REMOTE_NAME REMOTE_URL
# Determine what's being pushed
while read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
# Skip delete pushes
if [ "$LOCAL_SHA" = "0000000000000000000000000000000000000000" ]; then
continue
fi
# If the remote branch doesn't exist yet, compare against main
if [ "$REMOTE_SHA" = "0000000000000000000000000000000000000000" ]; then
REMOTE_SHA="origin/main"
fi
# Get changed content files
FILES=""
for PATTERN in $CONTENT_PATTERNS; do
MATCHED=$(git diff --name-only "$REMOTE_SHA" "$LOCAL_SHA" -- "$PATTERN" 2>/dev/null || true)
FILES="$FILES $MATCHED"
done
FILES=$(echo "$FILES" | xargs) # trim whitespace
if [ -z "$FILES" ]; then
echo "✅ No content files changed. Push proceeding."
exit 0
fi
echo "🛡️ PillarShield: checking $(echo "$FILES" | wc -w | xargs) content file(s)..."
echo ""
FAILED=0
CHECKED=0
BLOCKED=0
for FILE in $FILES; do
# Skip deleted files
if [ ! -f "$FILE" ]; then
continue
fi
CHECKED=$((CHECKED + 1))
CONTENT=$(cat "$FILE")
TITLE=$(basename "$FILE" | sed 's/\.[^.]*$//')
RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg key "$PILLARSHIELD_API_KEY" \
--arg content "$CONTENT" \
--arg title "$TITLE" \
'{
api_key: $key,
content: $content,
context: {
page_type: "markdown",
page_title: $title,
content_context: "ui:publish"
}
}')")
STATUS=$(echo "$RESPONSE" | jq -r '.status')
COMPLIANT=$(echo "$RESPONSE" | jq -r '.is_compliant')
REQUEST_ID=$(echo "$RESPONSE" | jq -r '.request_id // "N/A"')
if [ "$COMPLIANT" = "false" ]; then
BLOCKED=$((BLOCKED + 1))
FAILED=1
echo "❌ BLOCKED: $FILE"
echo " Status: $STATUS | Request ID: $REQUEST_ID"
echo "$RESPONSE" | jq -r '.violations[] | " → [\(.type)] \(.message)"'
echo ""
else
echo "✅ Passed: $FILE"
fi
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Checked: $CHECKED | Passed: $((CHECKED - BLOCKED)) | Blocked: $BLOCKED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "🚫 Push blocked. Fix the violations above and try again."
echo " If you believe this is a false positive, contact your PillarShield admin."
exit 1
fi
echo ""
echo "🛡️ All content passed governance. Pushing."
done
exit 0
What the developer sees
On a clean push:
$ git push origin main
🛡️ PillarShield: checking 3 content file(s)...
✅ Passed: docs/getting-started.md
✅ Passed: docs/api-reference.md
✅ Passed: blog/product-update.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Checked: 3 | Passed: 3 | Blocked: 0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🛡️ All content passed governance. Pushing.
On a violation:
$ git push origin main
🛡️ PillarShield: checking 3 content file(s)...
✅ Passed: docs/getting-started.md
❌ BLOCKED: docs/api-reference.md
Status: VIOLATION_DETECTED | Request ID: 4f1f0b77-8c5c-4b2a-9b25-0b3a2f5f2f33
→ [PII_PHONE] PII detected: phone number
→ [PII_EMAIL] PII detected: email address
✅ Passed: blog/product-update.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Checked: 3 | Passed: 2 | Blocked: 1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚫 Push blocked. Fix the violations above and try again.
If you believe this is a false positive, contact your PillarShield admin.
Notes
- Fail-open by default: If
PILLARSHIELD_API_KEYis not set, the hook prints a warning and allows the push. This prevents the hook from blocking developers who haven't configured it yet. - Override: Developers can bypass with
git push --no-verify. This is intentional. The hook is a safety net, not a prison. The audit log records the override decision if your CMS connector is also in the loop. - Content patterns: Edit the
CONTENT_PATTERNSvariable at the top to match your repo. If your content is in a specific directory, usecontent/*.mdinstead. - Team enforcement: For mandatory team-wide hooks, use Lefthook or Husky with the hook committed to the repo. Or enforce server-side with a CI check (see the GitHub Actions example above).
- Request IDs: Every response includes a
request_id. Include it if you contact support.
Response Handling
The API always returns HTTP 200 for governance decisions. Use the status and is_compliant fields to determine outcome.
Status Values
COMPLIANT— Content passed all checks.is_compliant: true.VIOLATION_DETECTED— One or more violations.is_compliant: false. Checkviolationsarray.AUTH_ERROR— Invalid or inactive API key.is_compliant: false.PLAN_LIMIT— Monthly quota exhausted.is_compliant: false.INTERNAL_ERROR— Server-side failure.is_compliant: false. Content was not evaluated.
Violation Types
Each violation includes type, severity (error, warning, info), and message.
- Deterministic:
PII_EMAIL,PII_PHONE,PII_SSN,PII_CARD,CREDENTIAL_LEAKAGE,GENERIC(prohibited terms) - AI-sourced:
PII_INDICATOR_SSN,PII_INDICATOR_CREDENTIAL,PII_INDICATOR_OTHER,TONE_ADVISORY,TONE_SCORE - Moderation:
MODERATION(hate, harassment, violence, sexual content)
Only error severity violations block content. warning and info are advisory.
Usage Quotas
Check your current usage programmatically.
Endpoint: POST /usage
curl -s -X POST https://api.pillarshield.co/pillarshield-governance/usage \
-H "Content-Type: application/json" \
-d '{"api_key": "psk_YOUR_KEY"}'
Returns:
{
"status": "OK",
"client_id": "CVE-1A2B3C4D",
"plan": "protect",
"governance_hub": "default",
"limits": {
"max_checks_month": 2000,
"max_llm_checks_month": 2000
},
"usage": {
"month_key": "2026-02",
"checks_total": 1245,
"checks_llm": 34,
"remaining_checks_month": 755,
"remaining_llm_checks_month": 1966
}
}
You can also query a specific month: {"api_key": "psk_...", "month_key": "2026-01"}
Best Practices
- Decide your failure mode. If the API is unreachable (network error, 5xx), should your app block the save (fail closed) or allow it with a warning (fail open)? PillarShield's AI checks are fail-open by design — if the AI service fails, only deterministic rules block. Your integration should make the same decision.
- Strip HTML before sending. The API evaluates text. While it handles some markup, stripping tags yields better results for prohibited term and PII detection.
- Send context. Include
page_typeandpage_titlewhen possible. It helps AI checks understand whether "confidential" in a Legal Policy page is expected vs. a blog post where it might indicate leaked internal content. - Use
request_idfor debugging. Every response includes a uniquerequest_id. Include it if you contact support or need to cross-reference with the audit log. - Monitor usage. Call
POST /usageperiodically or build it into your admin dashboard. If you're approaching your plan limit, add a usage block before you hit the cap.