Anthropic's Claude Skills let any developer extend an agent with a folder of code that runs on the user's machine. That is powerful, and dangerous. CertiK's new Skill Security Check API is the pre-execution control surface that catches malicious skills before they run. This guide walks you through everything you need to build with it: the mental model, the five integration patterns v1 ships for today, the response shape you actually need to handle, and a reference policy engine you can drop into production.\What you will buildBy the end of this guide you will have a working integration plan for any of five production-ready use cases. Marketplace publish gates, PR-time pre-merge checks, real-time agent admission, bulk repository audits, and enterprise compliance pipelines. Each is backed by a stable JSON contract you can parse once and reuse forever.\ The API is deliberately small. Four endpoints, three input modes, three scan depths, five risk categories, eighteen enumerated error codes. Everything is documented up front so you can write policy against it without surprises. If you have integrated Stripe webhooks or the Anthropic Messages API, this will feel familiar.\1. The 60-second mental modelA Claude Skill is a folder. Inside it lives a SKILL.md recipe in plain English with a YAML header, plus whatever helper scripts and dependencies the skill needs. When Claude uses the skill, that code runs on the user's machine with the user's permissions: full filesystem access, full network, full ability to read environment variables.\That is the threat surface. Help Net Security's June 2026 research found that 98% of production AI agents are exposed to what is called the "lethal trifecta": private data access plus untrusted content plus outbound actions. A malicious skill is exactly that trifecta wrapped in a friendly README. Researchers have already demonstrated live attacks delivered through skills.\The CertiK API is the gate you put in front of that surface. You hand it a skill, it hands back a score, a verdict, and a bounded findings list with stable IDs you can write policy against. That is the whole product, and the whole guide is about how to wire it correctly into the systems you already own.\2. Your first scan in 60 secondsSkip everything else and just run this. You will have a working scan response in under a minute.\curl -X POST $BASE_URL/v1/skill-security-check \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "github_repo": { "url": "https://github.com/your-org/your-skill" }, "options": { "scan_depth": "standard" } }'→ HTTP/1.1 200 OK (~2.1 s)Response:{ "scan_id": "scn_01HXC3K8QZ7M...", "status": "succeeded", "result": { "summary": { "findings_count": 1, "findings_truncated": false }, "overall_security": { "score": 94, "verdict": "pass" }, "categories": [ { "name": "malicious_code", "score": 96, "verdict": "pass" }, { "name": "data_leakage", "score": 95, "verdict": "pass" }, { "name": "shell_access", "score": 92, "verdict": "pass" }, { "name": "network_requests", "score": 90, "verdict": "pass" }, { "name": "file_system", "score": 94, "verdict": "pass" } ] }}That is the entire response surface. The envelope is the same shape on every scan. Clean skill, borderline skill, obviously malicious skill, same JSON. You write one parser and you are done. The only things that change are the numbers and the contents of findings[].\3. The scoring model: what gets measured and whyThe overall score is a fixed linear combination of five category scores. The weights are public, the verdict thresholds are exact integers, and there is no hidden math. This is the single most important design decision in the API. It means you can audit any policy decision back to a number.\Malicious code and data leakage carry the heaviest weights because they are the highest-blast-radius risks. File system access is the narrowest. Together they sum to 100, and the verdict thresholds (80, 40) are integers, no rounding ambiguity.In code:overall = 0.30 · malicious_code + 0.25 · data_leakage + 0.20 · shell_access + 0.15 · network_requests + 0.10 · file_systemThat is it. If a borderline skill comes in at data_leakage = 28 while everything else passes, the overall might land in the warn band but the category fail is the signal that actually matters. Always read the categories, not just the headline.\4. The integration architecture: four layers, one stable contractThe cleanest way to wire this in is the four-layer pattern below. The CertiK API is exactly one of those layers. Everything else lives in your codebase, which means you can swap providers later without changing your policy engine.\ \CertiK sits at two chokepoints: build-time (CI gating before a skill ships to the registry) and runtime (admission control before a skill executes on a user's machine). Same API, two scan points, same idempotency contract on both sides.\The two highest-leverage moves in this architecture: (1) normalise the response into a small internal shape before your policy engine sees it. That way you can change providers later without rewriting the engine. (2) Persist the raw scan_id forever. It is the only auditable truth when someone asks "why did we allow this skill three months ago."\5. Five integration patterns v1 ships for todayAlmost every real integration falls into one of five patterns. v1 covers all five. The table below maps each pattern to the endpoint, scan depth, mode, latency budget, and idempotency-key recipe. Pick the row that matches your workload, and the code in the following sub-sections is your starting point.\ \Marketplace gates run on standard. CI pre-merge runs on quick. Real-time admission runs on quick with caching. Bulk audit uses the repository-scans wrapper. Enterprise compliance uses async full with persistent scan_id retention.Pattern 1: Marketplace publish gateThe shape: a developer uploads a skill, your backend zips it, calls the API at standard depth, and either auto-approves on pass, queues for review on warn, or rejects on fail. The whole gate is roughly 50 lines of Python.\# marketplace_gate.pyimport base64, hashlib, requestsdef scan_skill(zip_bytes: bytes, submission_ref: str) -> dict: idem = hashlib.sha256(f"submission:{submission_ref}".encode()).hexdigest() r = requests.post( f"{BASE_URL}/v1/skill-security-check", headers={ "X-API-Key": API_KEY, "Idempotency-Key": idem, "Content-Type": "application/json", }, json={ "archive": { "data": base64.b64encode(zip_bytes).decode(), "filename": f"{submission_ref}.zip" }, "options": { "scan_depth": "standard", "include_evidence": True }, }, timeout=30, ) r.raise_for_status() return r.json()def decide(scan: dict) -> str: r = scan["result"] overall = r["overall_security"]["verdict"] cats = [c["verdict"] for c in r["categories"]] flags = r.get("execution_metadata", {}) if overall == "fail" or "fail" in cats: return "BLOCK" if flags.get("scan_degraded") or r["summary"]["findings_truncated"]: return "REVIEW" if overall == "warn" or "warn" in cats: return "REVIEW" return "ALLOW"\Pattern 2: PR-time pre-merge check (CI/CD)The shape: a GitHub Action runs on every PR that touches a skill. It calls the API with the PR's branch via github_repo, runs at quick depth, and fails the build on any non-pass verdict. Sub-second feedback for the developer.\# .github/workflows/skill-security.ymlname: skill-securityon: [pull_request]jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: CertiK skill scan env: CERTIK_API_KEY: ${{ secrets.CERTIK_API_KEY }} run: | IDEM="pr-${{ github.event.pull_request.number }}-${{ github.sha }}" RESP=$(curl -sS -X POST "$BASE_URL/v1/skill-security-check" \ -H "X-API-Key: $CERTIK_API_KEY" \ -H "Idempotency-Key: $IDEM" \ -H "Content-Type: application/json" \ -d '{ "github_repo": { "url": "https://github.com/${{ github.repository }}", "skill_path": "skills/my-skill" }, "options": { "scan_depth": "quick" } }') VERDICT=$(echo "$RESP" | jq -r '.result.overall_security.verdict') if [ "$VERDICT" != "pass" ]; then echo "::error::Skill scan returned: $VERDICT" echo "$RESP" | jq '.result' exit 1 fi\Pattern 3: Real-time agent admissionThe shape: at agent startup, before loading any skill, hash the skill's contents, look up the hash in your scan cache, and if it is missing or stale, run a sync quick scan. Sub-second admission, cache-amortised across all users of the same skill.\import { createHash } from "node:crypto";async function admitSkill(zipBuffer: Buffer): Promise { const hash = createHash("sha256").update(zipBuffer).digest("hex"); const cached = await redis.get(`scan:${hash}`); if (cached) return JSON.parse(cached).decision; const res = await fetch(`${BASE_URL}/v1/skill-security-check`, { method: "POST", headers: { "X-API-Key": API_KEY, "Idempotency-Key": `admit:${hash}`, "Content-Type": "application/json", }, body: JSON.stringify({ archive: { data: zipBuffer.toString("base64"), filename: `${hash}.zip` }, options: { scan_depth: "quick" }, }), }); const scan = await res.json(); const decision = scan.result.overall_security.verdict === "pass" ? "allow" : "deny"; await redis.setex(`scan:${hash}`, 86400, JSON.stringify({ decision, scan })); return decision;}\Pattern 4: Bulk repository auditThe shape: upload an archive containing many peer skills, get back one report per skill in a single response. This is the right tool for OSS namespace audits, mass marketplace ingest, or M&A due diligence on a skill catalog. v1 supports up to 20 discovered candidates per wrapper call.\# bulk_audit.py · scans every skill in a tarball, returns a verdict mapdef audit_repository(archive_bytes: bytes) -> dict: idem = hashlib.sha256(archive_bytes).hexdigest() r = requests.post( f"{BASE_URL}/v1/skill-security-check/repository-scans", headers={ "X-API-Key": API_KEY, "Idempotency-Key": idem, "Content-Type": "application/json", }, json={ "archive": { "data": base64.b64encode(archive_bytes).decode(), "filename": "namespace-audit.tar.gz" }, "options": { "scan_depth": "standard" }, }, timeout=300, ) r.raise_for_status() body = r.json() # body.items[*].response is the same single-skill shape as the base endpoint return { item["skill_path"]: decide(item["response"]) for item in body["items"] if item["status"] == "succeeded" }\Pattern 5: Enterprise compliance gateThe shape: long-running full-depth scan, async with persistent scan_id, response body persisted to an immutable audit log. The right pattern for SOC2 and ISO-27001 evidence collection and regulated environments.# compliance_gate.py · async full scan + audit log persistencedef compliance_scan(zip_bytes: bytes, skill_ref: str) -> dict: idem = hashlib.sha256(f"compliance:{skill_ref}".encode()).hexdigest() # 1. kick off the async scan create = requests.post( f"{BASE_URL}/v1/skill-security-check", headers={"X-API-Key": API_KEY, "Idempotency-Key": idem, "Content-Type": "application/json"}, json={ "archive": { "data": base64.b64encode(zip_bytes).decode(), "filename": f"{skill_ref}.zip" }, "options": { "scan_depth": "full", "include_evidence": True }, }, ) create.raise_for_status() scan_id = create.json()["scan_id"] location = create.headers["Location"] audit_log.append({"scan_id": scan_id, "skill": skill_ref, "started": now()}) # 2. poll every 5s minimum (per the rate-limit envelope) while True: time.sleep(5) poll = requests.get(f"{BASE_URL}{location}", headers={"X-API-Key": API_KEY}).json() if poll["status"] in ("succeeded", "failed"): audit_log.append({"scan_id": scan_id, "final": poll}) return poll\6. Building your policy engine: the right way to read the response\Every pattern above ends with the same question. What do you do with the response? The CertiK playbook is explicit on this. The verdict is an input to your decision, not the decision itself. Below is a reference policy engine that implements the recommended client model. Drop it in, tune the thresholds to your risk tolerance, and you are done.\# policy.py · the decision logic the playbook recommendsfrom dataclasses import dataclass@dataclassclass Decision: action: str # "ALLOW" | "REVIEW" | "BLOCK" reason: str scan_id: str findings: listdef evaluate(response: dict, mode: str = "conservative") -> Decision: # 1. is the scan even final? if response["status"] != "succeeded": return Decision("REVIEW", "scan_not_terminal", response["scan_id"], []) r = response["result"] verdict = r["overall_security"]["verdict"] cats = [c["verdict"] for c in r["categories"]] flags = r.get("execution_metadata", {}) # 2. hard blocks · never auto-allow these if verdict == "fail" or "fail" in cats: return Decision("BLOCK", "verdict_or_category_fail", response["scan_id"], r["findings"]) # 3. signal-quality issues · never auto-allow if r["summary"]["findings_truncated"] or flags.get("scan_degraded"): return Decision("REVIEW", "signal_incomplete", response["scan_id"], r["findings"]) # 4. policy mode determines what to do with warns if mode == "conservative": if verdict == "warn" or "warn" in cats: return Decision("REVIEW", "any_warn", response["scan_id"], r["findings"]) else: # balanced if verdict == "warn": return Decision("REVIEW", "overall_warn", response["scan_id"], r["findings"]) # 5. clean return Decision("ALLOW", "clean", response["scan_id"], []) \ The one-line invariant. Auto-allow only when status = succeeded AND overall.verdict = pass AND every category.verdict = pass AND scan_degraded ≠ true AND findings_truncated ≠ true. Anything else routes through review or block.\7. Production hardening: idempotency, retries, errorsThree things to get right before you ship. Idempotency, the retry policy, and the error envelope. The API is designed so all three are easy.7.1 Idempotency is required, not optionalEvery create request needs an Idempotency-Key header. Same key plus same canonical body returns the same scan_id and the same response body. Same key with a changed body returns 409 idempotency_conflict, which is exactly the error you want, because it tells you the retry logic is broken before it costs you money. The deterministic recipe: sha256(submission_ref || mode || depth).7.2 Retry policy: respect the envelopeRate limits are conservative and documented up front: 30 RPM aggregate, 4 concurrent sync, 2 in-flight async, poll cadence ≥ 5 s. Wrapper calls (batch and repository-scans) count as 20× weighted against the window. On HTTP 429, pause and retry after the Retry-After header. On HTTP 503 scan_runtime_unavailable, use bounded exponential backoff with jitter. Never raise parallelism while retrying.7.3 Error envelope: every code is enumeratedEighteen public error codes cover every documented failure mode. authentication_failed, ambiguous_skill_discovery, invalid_dependency_manifest, idempotency_conflict, and so on. Each one has a stable string code and an HTTP status. Map them once into your client and forget about it.RETRY_CODES = {"rate_limit_exceeded", "transient_upstream_error"}HARD_FAIL = {"invalid_archive", "unsupported_skill_shape", "idempotency_conflict", "manifest_parse_error"}NEEDS_REVIEW = {"ambiguous_skill_discovery", "invalid_dependency_manifest"}\8. Proof it works: six fixtures, six honest answersBefore integrating any scanner, you want to see it actually catch things. I ran six fixture skills through the API spanning the realistic risk spectrum. A clean baseline, a borderline benign-but-flaggable skill, a quietly leaky one, an obviously malicious one, a skill with a malformed manifest, and a skill with a known-vulnerable dependency. The scanner graded each one the way a careful human reviewer would.\ Two clean passes (96, 93). Two passes with warn signals in their category breakdowns (84, 81). One category-fail-with-overall-warn that a strict policy engine still blocks on (59). One hard fail (28). One degraded success that the response flags via scan_degraded. Each cell is the actual category score returned by the API.\The interesting case for integration is env-mirror: overall lands at 59 (warn band), but data_leakage alone is at 28 (category fail). The scanner correctly surfaced that one category, and a well-written policy engine blocks on it. This is the integration test you should run yourself before shipping.\9. Performance: sub-second to sub-half-minute, predictablyPick the depth that matches your latency budget. quick is sub-second and good enough for the gate use cases. standard is the marketplace sweet spot. full is the deepest tier, async-only, and lands in the 25-35 second range.\quick averaged 612 ms. standard averaged 2,083 ms. full (async) averaged 28.4 seconds end-to-end from POST to terminal status. The shaded blue band marks the 1-3 second marketplace publish-gate sweet spot.\10. The market context: why CertiK is the right vendorThis is not a startup taking a flyer. CertiK is the dominant Web3 security firm. A $2B valuation, $296M raised from Tiger Global, Coinbase, and Insight Partners, roughly 65% of the global blockchain audit market, and roughly $600B in crypto assets monitored by its Skynet platform. The Skill Security Check API is the company deliberately stepping out of pure Web3 audit and into the broader agentic AI security market.\| Metric | Value | Detail ||----|----|----|| CertiK valuation | $2.0B | IPO targeted as first public Web3 cybersecurity listing || Total funding | $296M | Tiger Global · Coinbase · Insight Partners || Blockchain audit market share | ~65% | global market leader || Assets secured | $600B+ | via Skynet continuous monitoring |\ From $1.65B in 2026 to $13.52B in 2032, a 42% CAGR. Skill scanning is one of the foundational control surfaces inside this market. CertiK arrives early, with an existing $600B-asset trust signal that translates directly.\ The demand signal is brutal. Only 11% of production AI agents pass the security bar in recent independent research. 83% of claimed defences lack independent verification. The market is large, growing fast, and underserved. CertiK is the rare vendor that can translate an existing trust signal (the Web3 Security Leaderboard, the Skynet platform) into the new market without rebuilding credibility from scratch. This is the environment in which Anthropic is scaling Claude to $43B in ARR and 300,000+ business customers.\11. What is shipping next in v1.xv1 covers the five canonical patterns above. The v1.x roadmap focuses on the integrations developers are asking for. Webhook delivery so async full does not require polling. Private GitHub and GitHub Enterprise so regulated teams can scan in-place. Node and TypeScript dependency manifests so the JS skill ecosystem gets the same depth Python does today. Treating prompt-injection-inside-SKILL.md as a first-class category alongside the existing five. Watch CertiK's product channel for the v1.1 announcement.\Get started todayIf you ship anything that loads third-party Claude Skills, a marketplace, a publishing pipeline, a CI gate, an enterprise compliance flow, the integration cost is one afternoon. Here is the checklist:Request API access from CertiK and store the API key in your secrets manager.Pick one of the five patterns above based on your workload (the table in Section 5 is the lookup).Drop in the policy engine from Section 6 as your starting point. Tune thresholds to your risk tolerance.Wire your audit log to persist scan_id forever. It is your forever truth.Run your six fixtures: clean, borderline, leaky, malicious, malformed manifest, vulnerable dep. Make sure the integration handles each correctly.Ship.\CertiK Skill Security Check API v1 · June 2026.Author: Ishan Pandey ·\:::tipVested Interest Disclosure: HackerNoon has reviewed the report for quality, but the claims herein belong to the author. #DYOR:::\