Shield.
Documentation · API reference

Shield API

REST + npm packages for obfuscating JavaScript and HTML from your build pipeline. Same engine as the dashboard, same monthly quota, same self-defending output. Available on every plan — including Free.

Install

Three official npm packages. Pick whichever fits your workflow — they all hit the same REST API under the hood.

bash
# CLI (CI / scripts / ad-hoc)
npm install --save-dev shieldmycode-cli

# Webpack 5
npm install --save-dev shieldmycode-webpack-plugin

# Vite / Rollup
npm install --save-dev shieldmycode-vite-plugin

Requires Node 18+. The CLI binary is exposed as shield(and shieldmycode as an alias).

Authentication

Every authenticated endpoint uses a Bearer token. Create a personal key at Dashboard → API keys (every plan). Keys are 32 random bytes prefixed with shield_. Treat them like passwords — they authenticate against your account and consume your monthly quota.

bash
# Header on every request
Authorization: Bearer shield_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Or as the SHIELD_API_KEY env var for the CLI / plugins
export SHIELD_API_KEY=shield_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys never expire on their own. Rotate them from the dashboard whenever a teammate leaves or you suspect leakage.

First API call

Smoke-test from your terminal:

bash
curl -s -X POST https://shield.shieldmycode.com/api/v1/obfuscate \
  -H "Authorization: Bearer $SHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "const secret = \"alpha\"; function unlock(c) { return c === secret; }",
    "kind":   "js",
    "options": { "level": "hard", "antiLlm": true }
  }' | jq .

You should get back something like:

json
{
  "output":     "(function(){\"use strict\";var _0xH=\"...\";...})();",
  "bytesIn":    67,
  "bytesOut":   12450,
  "durationMs": 8,
  "kind":       "js",
  "level":      "hard",
  "quota":      { "used": 1, "limit": 3, "plan": "free" }
}

POST /api/v1/obfuscate

Obfuscate a single JavaScript or HTML source. The workhorse endpoint: the CLI, Webpack plugin, and Vite plugin all call this under the hood.

Method
POST
URL
/api/v1/obfuscate
Auth
Bearer token (required)
Rate limit
Plan monthly quota (3 / 1,000 / 10,000 / custom)

Request body

json
{
  "source": "string (required) — JS or HTML source to obfuscate. Max 50 MB.",
  "kind":   "\"js\" | \"html\" (required)",
  "options": {
    "level":            "\"soft\" | \"medium\" | \"hard\" (default \"hard\")",
    "encodeStrings":    "boolean (default true)",
    "obfuscateNumbers": "boolean (default true)",
    "mangleLocals":     "boolean (default true)",
    "integrity":        "boolean (default true)",
    "debuggerTrap":     "boolean (default true)",
    "devToolsCheck":    "boolean (default true)",
    "heartbeatMs":      "number (50–5000, default 250)",
    "noscriptFallback": "boolean (default true)",
    "stealth":          "boolean (default false) — HTML only",
    "headlessCheck":    "boolean (default true)",
    "antiLlm":          "boolean (default true) — anti-LLM defenses",
    "deadCode":         "boolean (default false) — inject decoy branches",
    "domainLock":       "string — comma-separated hostnames",
    "expiresAt":        "string — ISO 8601 datetime",
    "geoAllow":         "string — comma-separated ISO 3166-1 country codes",
    "browserBlocklist": "string — comma-separated UA substrings",
    "osBlocklist":      "string — comma-separated OS names",
    "ipBlocklist":      "string — comma-separated IPv4/IPv6",
    "telemetryUrl":     "string — POST tamper events here"
  }
}

Response (200 OK)

json
{
  "output":     "string — the obfuscated payload",
  "bytesIn":    "number — input size",
  "bytesOut":   "number — output size",
  "durationMs": "number — server-side processing time",
  "kind":       "\"js\" | \"html\"",
  "level":      "the strictness that ran",
  "quota":      { "used": 1, "limit": 1000, "plan": "pro" }
}

Examples

curl
curl -X POST https://shield.shieldmycode.com/api/v1/obfuscate \
  -H "Authorization: Bearer $SHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d @payload.json
Node.js
// Node 18+ (uses native fetch)
const SOURCE = require('fs').readFileSync('app.js', 'utf8');

const r = await fetch('https://shield.shieldmycode.com/api/v1/obfuscate', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.SHIELD_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    source: SOURCE,
    kind:   'js',
    options: { level: 'hard', antiLlm: true, domainLock: 'mycompany.com' }
  })
});
if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
const { output, quota } = await r.json();
require('fs').writeFileSync('app.shield.js', output);
console.log(`${quota.used}/${quota.limit} this month`);
Python
# Python 3.8+
import os, requests

src = open('app.js').read()
r = requests.post(
    'https://shield.shieldmycode.com/api/v1/obfuscate',
    headers={ 'Authorization': f"Bearer {os.environ['SHIELD_API_KEY']}" },
    json={
        'source': src,
        'kind':   'js',
        'options': { 'level': 'hard', 'antiLlm': True }
    },
    timeout=30,
)
r.raise_for_status()
data = r.json()
open('app.shield.js', 'w').write(data['output'])
print(f"{data['quota']['used']}/{data['quota']['limit']} used")

POST /api/batch/zip

Upload a .zip archive of mixed HTML + JS files and get back a protected archive of the same shape. Static assets (CSS, images, fonts) pass through unchanged. Business plan and up.

Method
POST (multipart/form-data)
URL
/api/batch/zip
Auth
Session cookie (dashboard only today)
Hard limits
50 MB zip · 5,000 entries · per-file quota

Each .js / .mjs / .cjs /.html / .htm entry counts as one obfuscation against your monthly quota. The response includes custom X-Shield-* headers (X-Shield-Files-Processed, X-Shield-Files-Errored, X-Shield-Bytes-In, X-Shield-Bytes-Out, X-Shield-Stopped-Early) for batch stats.

POST /api/telemetry/[userId]

Receives tamper events from obfuscated code in the wild and feeds them into your threat-intel dashboard. You don’t call this directly — the obfuscated payload calls it automatically when options.telemetryUrl is set to your personal endpoint (visible on the threat-intel page).

No auth header required — the URL itself embeds an opaque user id that’s rate-limited and verified server-side. Bounded retention: 10,000 events per account; older events rotate out.

Options reference

All options are optional. Defaults shown below match the dashboard’s “Hard” preset.

KeyTypeDefaultScopeNotes
levelenum"hard"allsoft / medium / hard
encodeStringsbooleantrueallString array (XOR + base64)
obfuscateNumbersbooleantrueallNumeric expression replacement
mangleLocalsbooleantrueallScope-aware identifier rename
antiLlmbooleantrueallSemantic decoy rename + IIFE wrap
deadCodebooleanfalseallInject decoy branches
integritybooleantrueallFNV-1a hash check
debuggerTrapbooleantrueallTiming-based debugger detection
devToolsCheckbooleantrueall3-signal DevTools detection
heartbeatMsnumber250all50–5000, watchdog interval
headlessCheckbooleantrueallPuppeteer / Playwright / Selenium
stealthbooleanfalseHTML onlyHide visible body in payload
noscriptFallbackbooleantrueHTML onlySEO-safe <noscript> mirror
domainLockstring""allComma-separated hostnames
expiresAtstring""allISO 8601 datetime
geoAllowstring""allISO 3166-1 alpha-2 list
browserBlockliststring""allUA substrings, comma-separated
osBlockliststring""allWindows, Mac, Linux, Android, iOS, ChromeOS
ipBlockliststring""allIPv4 or IPv6 list
telemetryUrlstring""allPOST tamper events here

Rate limits & quotas

Monthly quotas are the only rate limit in normal use. No per-second throttle today. Use whatever concurrency makes sense for your pipeline; we recommend 5–10 in flight.

PlanMonthlyPer-fileAPIZIP batch
Free350 KB
Pro1,0005 MB
Business10,00050 MB
EnterpriseCustom500 MB

Quotas reset on the 1st of each calendar month (UTC). Hitting the limit returns 429 over_quota with your current count and the plan limit.

Error codes

All errors return JSON with an error string and (for most) a detail message. HTTP status codes map directly to the cause.

StatuserrorMeaning & fix
400bad_requestSchema validation failed. Check field types.
401unauthorizedMissing / unknown / revoked Bearer token.
403level_not_allowedStrictness not allowed on your plan.
403zip_batch_not_allowedZIP batch requires Business+.
413input_too_largeSource exceeds plan maxInputBytes.
413zip_too_largeArchive exceeds 50 MB / 5,000 entries.
429over_quotaMonthly limit hit. Upgrade or wait.
500obfuscation_failedEngine threw. Likely unparseable source.

shieldmycode-cli

Walks files, directories, or globs. Parallel by default (5 concurrent). Auto-detects file kind from extension.

bash
# single file -> next to it
SHIELD_API_KEY=shield_xxx npx shield obfuscate app.js

# explicit output
npx shield obfuscate app.js -o dist/app.shield.js

# whole dist/ tree, mirrored under dist-shield/
npx shield obfuscate dist -o dist-shield --recursive

# glob with config file (shield.config.js auto-detected)
npx shield obfuscate "src/**/*.{js,html}" -o dist-shield

Full flag reference: npx shield --help.

shieldmycode-webpack-plugin

Hooks into Webpack 5 right after the minifier. Every npm run build ships protected. Source maps and other non-JS/HTML assets pass through.

js
// webpack.config.js
const ShieldPlugin = require('shieldmycode-webpack-plugin');

module.exports = {
  // ...your existing config...
  plugins: [
    new ShieldPlugin({
      apiKey: process.env.SHIELD_API_KEY,
      options: {
        level: 'hard',
        antiLlm: true,
        domainLock: 'mycompany.com'
      }
    })
  ]
};

shieldmycode-vite-plugin

Hooks into Rollup’s generateBundle stage. Only runs during vite build — HMR stays instant.

js
// vite.config.js
import { defineConfig } from 'vite';
import shield from 'shieldmycode-vite-plugin';

export default defineConfig({
  plugins: [
    shield({
      apiKey: process.env.SHIELD_API_KEY,
      options: { level: 'hard', antiLlm: true }
    })
  ]
});

CI / GitHub Actions

yaml
name: Build & protect
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci && npm run build
      - run: npx shieldmycode-cli obfuscate dist -o dist-protected --recursive
        env:
          SHIELD_API_KEY: ${{ secrets.SHIELD_API_KEY }}
      - uses: actions/upload-artifact@v4
        with: { name: protected, path: dist-protected }

Bash loop over /dist

bash
#!/usr/bin/env bash
# Obfuscate every .js in ./dist into ./dist-protected
mkdir -p dist-protected
for f in dist/**/*.js; do
  rel="${f#dist/}"
  out="dist-protected/${rel}"
  mkdir -p "$(dirname "$out")"
  source=$(cat "$f")
  curl -sS -X POST https://shield.shieldmycode.com/api/v1/obfuscate \
    -H "Authorization: Bearer $SHIELD_API_KEY" \
    -H "Content-Type: application/json" \
    -d "$(jq -nc --arg s "$source" '{source:$s, kind:"js", options:{level:"hard"}}')" \
    | jq -r '.output' > "$out"
  echo "  $f -> $out"
done

Python wrapper

python
# shield.py — minimal Shield API wrapper
import os, requests, pathlib

API_KEY = os.environ['SHIELD_API_KEY']
BASE    = 'https://shield.shieldmycode.com'

def obfuscate(source: str, kind: str = 'js', **options) -> dict:
    r = requests.post(
        f'{BASE}/api/v1/obfuscate',
        headers={ 'Authorization': f'Bearer {API_KEY}' },
        json={ 'source': source, 'kind': kind, 'options': options },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

# Usage
src = pathlib.Path('app.js').read_text()
result = obfuscate(src, level='hard', antiLlm=True)
pathlib.Path('app.shield.js').write_text(result['output'])
print(f"{result['quota']['used']}/{result['quota']['limit']} this month")

Ready to wire it up?

Create a free account, generate an API key, and have your first build-time obfuscation running in under five minutes.

API documentation · Shield