Documentation

MILLENNIUMS.AI documentation

Point MILLENNIUMS.AI at your AI application and it hacks it for you — in a private, throwaway sandbox — then hands back the vulnerabilities it proved, with a working exploit and a plain fix for each one.

You don't need a security team or a single line of attack code. Give it a target (a URL, a repository, or an API), and autonomous agents probe the AI attack surface the way a real attacker would: prompt injection, tool and agent abuse, data leakage, RAG flaws, runaway cost, and the rest of the OWASP LLM Top 10. Every finding you see is one it actually pulled off, with the exact steps to reproduce it.

This site has three parts. Core concepts explains what the tool does and why. Guides walk you through real tasks. The API reference documents every endpoint so you can drive scans from CI or your own scripts.

New here? Start with the browser quickstart — you'll have a real, provable finding in a few minutes, no code required.

Quickstart — in the browser

The fastest way to see a result. No install, no card.

  1. Create your account. Go to scan.millenniums.ai/app and sign up with your email. A work email is best; a personal one works too. You get a free scan to start.
  2. Verify your email. Click the link we send you. This unlocks scanning. (Didn't arrive? Use "Resend" in the app.)
  3. Point it at a target. Paste your app's staging URL — the live address where it's running (not production). Optionally drag in your code (a .zip) for a deeper scan. Press Start a scan.
  4. Watch it work. The scan runs in an isolated sandbox and typically finishes in minutes. You'll see it probe each category live.
  5. Read the findings. Each proven vulnerability comes with its severity, impact, and a plain remediation. On a paid plan, every finding also includes a working proof of concept and a draft fix.
Only scan what you're allowed to. The tool actively tries to exploit whatever you point it at. Use it against your own apps, or ones you have explicit permission to test. See Terms.

Quickstart — with the API

Prefer to drive it from a terminal or CI? Everything the app does is a REST call. Your access token is created at signup and shown in the app; it goes in an Authorization: Bearer header.

1. Check your account

Confirm your token works and see your plan and remaining quota.

curl https://scan.millenniums.ai/api/me \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{ "tenant": "acme-3f9a2c", "plan": "starter", "poc": true,
  "scans_used": 1, "scans_limit": 5, "verified": true }

2. Start a scan

Give it a target. Optionally add source (a repo or path) for a much deeper white-box scan, and a budget spend cap.

curl -X POST https://scan.millenniums.ai/api/scan \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target":"https://staging.yourapp.com/chat","budget":10}'
{ "run_id": "20260731-abc123" }

3. Read the results

Poll the run until running is false, then read its findings.

curl https://scan.millenniums.ai/api/runs/20260731-abc123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

That's the whole loop: mescanruns/<id>. The API reference documents every field.

How it works

Five stages, all automatic:

  1. Point. You register a target: a staging URL, a repository, or an API endpoint.
  2. Attack. Autonomous agents spin up in an isolated Docker sandbox and probe the AI attack surface — no scripts for you to write.
  3. Prove. When an agent finds a weakness, it tries to exploit it for real. Only exploits that actually fire become findings; unproven leads are held separately.
  4. Fix. Each finding comes with a plain remediation and, when you want it, a draft pull request your engineers review.
  5. Gate. On the CI plans, a scoped scan runs on each pull request and blocks the merge on a proven, net-new vulnerability.

When the scan ends, the sandbox — and everything in it — is destroyed.

What to point it at

The single most common question, answered up front: you give us a target, and optionally your source. They're not the same thing.

Target (required)Source (optional)
WhatYour live, running app — a URL or API endpoint that answers requests right now.Your code — a git repo or an uploaded .zip.
WhyIt's what the scanner actually attacks.Lets the scanner read the code as it attacks (white-box) — deeper findings.
Examplehttps://staging.yourapp.com/chatgithub.com/acme/app or my-app.zip
Code is not a running app. Uploading a zip or linking a repo does not let us find or start your app — we can't run your code for you. We attack your app where it's already running. So you always give a target URL; the source is an extra on top of it, never a replacement.

Staging vs production — always aim at staging

  • Production is the real app your customers use, with real data.
  • Staging is a separate, running copy in a test environment — the same app, but with no real customers or data.

The scanner is a real attacker: it submits inputs, can create records, trigger actions your app exposes, and drive up model cost against your app. On staging that's harmless. On production it can mean junk data or real side effects for real users. Only scan production if you understand and accept that (a test account and a quiet time help).

How do I know my URL? A staging URL looks like…

It's just a web address where your app runs. Common shapes:

  • staging.yourapp.com or app-staging.yourapp.com
  • A preview URL from Vercel or Netlify (they mint one per change)
  • A *-staging.onrender.com or *.herokuapp.com app
  • A local address (localhost:3000) exposed to the internet with a tunnel (ngrok, cloudflared)

Easiest: let the app find it for you

In the app, paste the one link you already know — your main website or app address — into "Not sure what to scan?" and press Find what to scan. We look at the page, spot the AI features and API endpoints, and check public records for your other environments (like staging. and app.). You get back a short list of recommended targets — click one to scan it. No hosting logins, no hunting.

Find it yourself — no developer needed

Prefer to do it by hand, or want to double-check? You don't need anyone technical. Two reliable ways:

  1. Open your own app and copy the link. Use your product the way a customer does — go to the page with the AI feature (the chat, the assistant) and copy the address straight from your browser's address bar. That address is your target.
  2. Follow the money to your hosting account. Your app runs somewhere, and whoever's card pays the hosting bill can see every URL. Check your statements for Vercel, Netlify, Render, Heroku, AWS, DigitalOcean, Google Cloud, log into that account, and the dashboard lists your app's addresses — production and any staging/preview ones.

Backup routes: your domain registrar / DNS (GoDaddy, Namecheap, Cloudflare — wherever you bought the domain) shows subdomains like staging., app., api.; or ask your hosting provider's support from inside your account.

Still have a developer or agency? The fastest path is to ask them "what's our staging URL?" — but if that's not an option, the two steps above get you there on your own.

I don't have a staging URL — how do I get one?

  • Most hosts spin up a staging copy in one click from your account — Vercel, Netlify, and Render all offer free preview/staging environments, no code required.
  • If your app is on GitHub, a preview URL is often generated automatically on each pull request.
  • Only have production? You can still scan it — just treat it as a live attack (quiet time, a test account, expect some junk data). See Staging vs production above.
  • No one technical at all? A freelancer can stand up staging in an afternoon — then you paste that URL here.

Once you have the URL, follow Run your first scan. Add your code (upload or repo) for a deeper white-box scan.

Black-box vs white-box

How much you give the scanner decides how much it finds.

  • Black-box — you give only a live URL or API. The scanner attacks from the outside, like an anonymous attacker. Fast, zero setup, but it can only find what's reachable from the front door.
  • White-box — you also give the application's source. The scanner reads the code as it attacks, so it understands the tools, prompts, and data paths behind the endpoint. This is a large uplift in both the number and the depth of findings.

Two ways to hand over the source — you don't need to know git:

  • Upload a .zip of your project. In the app, drop it into the scan box; over the API, upload it and pass the returned upload_id. Your code is held only for that scan and deleted when it ends — never used to train.
  • Point at a git repo — GitHub, GitLab, Bitbucket, or Azure DevOps. Paste the repo URL in the source field, or in the app Connect GitHub once and pick a repo (including private ones) from a dropdown. For other hosts, paste the URL; private repos there use the .zip upload.
Recommendation: for AI apps, always provide source when you can. A URL alone frequently misses injection and tool-abuse paths that are obvious once the code is in view. Not the person who holds the code? Have your developer do the upload.

What it tests

Every category on the OWASP LLM Top 10, mapped to MITRE ATLAS:

  • Prompt injection
  • Sensitive information disclosure
  • Supply chain
  • Data & model poisoning
  • Improper output handling
  • Excessive agency (tool & agent abuse)
  • System prompt leakage
  • Vector & embedding weaknesses (RAG)
  • Misinformation
  • Unbounded consumption (runaway cost)

The full matrix, with the mapping to MITRE ATLAS, is published on the Trust Center.

Findings & proofs of concept

A finding is a vulnerability the scanner proved, not a guess. Each one carries:

FieldWhat it is
titleOne-line summary of the vulnerability.
severitycritical high medium low — impact-ranked.
cwe / cvssStandard weakness ID and CVSS score, when applicable.
impactWhat an attacker gains in plain terms.
technical_analysisWhy it works, for an engineer.
remediationThe concrete fix.
pocA working, re-runnable proof of concept. Paid plans only.
endpoint / method / locationsWhere it lives — the request and the code locations.
Proof of concept lock. On the free tier, findings are fully visible but the working exploit (poc) is held back. Upgrading to any paid plan unlocks it on all findings, including past ones.

Spend caps & safety

Autonomous agents call a language model, which costs money. Two independent limits make sure a scan can never surprise you:

  • Per-scan budget cap. Every scan takes a budget (US dollars, default 10). The engine stops when it hits that ceiling.
  • Token watchdog. A separate backstop kills any scan that blows past a hard token count, even for a self-hosted model the pricing layer can't see. So a runaway loop can't drain your account.

Every scan also runs in its own isolated sandbox, and concurrency is capped per plan so you can't accidentally launch a hundred scans at once.

Authorization & domain verification

Because a scan is a real attack, you may only scan an app you own or are authorized to test. To make that more than a promise, the first time you scan a new public domain we ask you to prove you own it — one time per domain. This stops anyone from pointing us at a competitor.

Verify by either method (pick whichever you can reach — you only need one):

  • Meta tag (easiest). Add a line to your homepage's <head>. Most site builders — Wix, Squarespace, Webflow, Shopify — have a "header code" or "site verification" box for exactly this:
    <meta name="millenniums-verification" content="YOUR-TOKEN">
  • DNS TXT record. Add a TXT record at your domain registrar:
    millenniums-verification=YOUR-TOKEN

Verifying the root domain covers all its subdomains — verify acme.com once and you can scan staging.acme.com, api.acme.com, and the rest. You'll also confirm a short authorization certification (that you own the app, or are authorized to test it) before the first scan runs.

Exempt: local and private targets — localhost, a private IP, host.docker.internal — need no verification. That's your own machine.

Verify in the app when prompted, or over the API at POST /api/domains/verify. Discovery (reading a public page) needs no verification — only scanning does.

Your data

Your code and scan results are yours. Each scan runs in a throwaway sandbox that is destroyed when the scan finishes, so we hold as little of your code as possible and only for as long as the scan needs it. We never use your code or results to train any model. Full details, including sub-processors, are in the Trust Center and Privacy Policy.

Traceability report

Every scan produces a chain-of-custody report so you can prove — to yourself, a customer, or an auditor — exactly what happened to your data. It's not a marketing claim; each line is derived from a recorded event in the scan's lifecycle.

The report includes:

  • A timestamped timeline: scan created → source received → isolated sandbox launched → scan finished → sandbox torn down → (for uploads) uploaded source deleted.
  • Source provenance: whether the scan was black-box (live target only), or white-box from an upload or a git repo. Git URLs are recorded by host only — no paths or credentials.
  • Attestations: isolated sandbox, sandbox torn down (verified against the container runtime), uploaded source deleted, spend cap enforced, and never used to train a model.
  • Usage: tokens and LLM calls for the run.

Get it in the app on any run under Data trail — chain of custody (with a one-click JSON download), or over the API at GET /api/runs/{run_id}/trace.

Why it matters: when a customer or regulator asks "what did you do with our code?", you hand them this — a per-scan record showing the code stayed sandboxed, the sandbox was destroyed, and nothing was retained or used for training.

Guide: run your first scan

Goal: go from a fresh account to a proven finding.

Prerequisites

  • A verified account (browser quickstart).
  • A target URL — a live, running instance of your app that you own or may test. Use a staging URL, not production. This is required; your code alone isn't a running app. Don't have one? See What to point it at.
  • Optional: your source — a git repo URL or a .zip upload, for a deeper white-box scan.

Steps

  1. Open the app and paste your target into the scan box, or call POST /api/scan.
  2. If you have the source, add it — a repo URL or local path in the source field. This is the single biggest lever on finding quality.
  3. Start the scan. Note the run_id.
  4. Wait for it to finish (status runningfalse). Minutes, typically.
  5. Open the report and triage from the top: criticals first.

Troubleshooting

  • 403, "verify your email" — click the verification link, or hit "Resend" / POST /api/resend.
  • 402, quota reached — you've used your included scans. Add a card / upgrade (plans).
  • 500, "scan did not start" — the engine couldn't launch (Docker or the model key). For self-hosted, check your .env; on our hosted app, retry.
  • Zero findings on a URL-only scan — provide source and re-run. Black-box alone often misses AI-specific paths.

Guide: register a target

If you scan the same app repeatedly, register it once as a named target instead of pasting it every time. A target stores the target, an optional name, the mode, the budget, and the source. Register with POST /api/targets and list them with GET /api/targets. Registered targets are what the CI plans scan automatically on each pull request.

Guide: read a scan report

A run has a status, a live running flag, a findings_count, and the findings array. Work it like this:

  1. Sort by severity. Fix criticals and highs first; they're the ones an attacker reaches soonest.
  2. Read the poc. On paid plans, reproduce it yourself — it's re-runnable — to confirm and to understand the attack.
  3. Apply the remediation. Each finding names the concrete fix and the locations in code.
  4. Re-scan. Run it again to confirm the fix closed the hole.

Guide: scan on every pull request

On the Developer plan and up, a scoped scan runs on each pull request that touches your AI surface and blocks the merge on a proven, net-new vulnerability. Recurring findings are de-duplicated, so the pipeline stays quiet until something real appears. On the Team plan, a confirmed finding can also open a draft fix pull request your engineers review — nothing merges on its own. Review status is available at GET /api/pr-reviews.

Guide: ask questions about a scan

Not sure what a finding means, or how to fix it in your stack? Ask. POST /api/chat takes a list of messages and, optionally, a run_id so the answer is grounded in that specific scan's findings. Use it to turn a report into a fix plan.

Plans, quota & overage

Pick a monthly plan; add scans as you grow. Full pricing is on the pricing page.

PlanScansWorking PoCConcurrentBuilt for
FreeA scan to start1Trying it out
Starter5 / mo2Solo builders
Developer15 / mo✓ + CI3Growing teams
Team40 / mo✓ + CI + autofix PR4Shipping on every release
EnterpriseUnlimited (fair use)✓ On-prem / BYO-key8Regulated products
  • What's a scan? One run against one target. A pull-request check and a full scan each count as one.
  • Overage. On paid plans, scans beyond your monthly quota bill at the posted per-scan rate ($79) rather than blocking you.
  • Free tier. A one-time allowance (never resets) so you can see a real finding before you decide. The working exploit and autofix unlock on any paid plan.

Start a self-serve upgrade from the app, or with POST /api/billing/checkout. Enterprise is sales-led — book a walkthrough.

API — Authentication

Every request except signup and the Stripe webhook is authenticated with a bearer token. Your token is created at signup and shown in the app.

Authorization: Bearer YOUR_ACCESS_TOKEN

A missing or unknown token returns 401 Unauthorized. Keep your token secret — it grants full access to your account's scans and findings. If it leaks, sign in and rotate it.

API — Base URL & conventions

  • Base URL: https://scan.millenniums.ai
  • Content type: requests and responses are JSON. Send Content-Type: application/json on POSTs.
  • Success: 200 OK with a JSON body.
  • Errors: a non-2xx status with { "error": "message" }. See status codes.

API — Account

GET/api/me

Your account, plan, and quota. The quickest way to confirm a token works.

FieldTypeMeaning
tenantstringYour account ID.
planstringfree · starter · developer · team · enterprise.
pocboolWhether working PoCs are unlocked on your plan.
scans_used / scans_limitintUsage against your allowance.
verifiedboolEmail verified — required to scan.
POST/api/signup

Public. Create an account and get a token. A verification email is sent automatically.

BodyTypeRequiredNotes
emailstringyesWork email preferred. Disposable domains are rejected.
companystringnoUsed to name your account.
curl -X POST https://scan.millenniums.ai/api/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"you@yourcompany.com","company":"Acme"}'
{ "token": "…", "tenant": "acme-3f9a2c", "plan": "free", "verified": false }
POST/api/resend

Re-send the verification email to your account's address. Requires your token.

API — Scans

POST/api/discover

Turn one public link into scannable targets. Give the url of your site or app; we fetch the page, detect the AI surface (chat widgets, API/AI endpoints, LLM providers) and stack, and enumerate sibling environments from public certificate-transparency logs. Read-only — it doesn't attack anything.

BodyTypeNotes
urlstringRequired. A public https:// website or app link.
{ "root_domain": "acme.com",
  "ai_surface": [ {"type":"chat-widget","name":"Intercom"}, {"type":"api-endpoint","path":"/api/chat"} ],
  "environments": [ "staging.acme.com", "api.acme.com" ],
  "recommended_targets": [ "https://acme.com/api/chat", "https://staging.acme.com" ] }

Pick one of recommended_targets and pass it as the target to /api/scan.

POST/api/upload

Upload your application's source as a .zip for a white-box scan — no git required. The body is the raw zip bytes (Content-Type: application/zip). Returns an upload_id you pass to /api/scan. The upload is stored only for your account, used for that one scan, and deleted when it finishes. Max 100 MB.

curl -X POST https://scan.millenniums.ai/api/upload \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/zip" \
  --data-binary @my-app.zip
{ "upload_id": "7Qb2x9Za" }
Non-technical owner? You don't need to touch git or the API. In the app, just drag your project .zip into the scan box — or send it to whoever manages your code and have them do it.
POST/api/scan

Start a scan against a target. Gated on email verification, your remaining quota, and your plan's concurrency limit.

BodyTypeDefaultNotes
targetstringRequired. A URL, repository, or API endpoint to attack.
upload_idstringnoneOptional. The id from POST /api/upload — a white-box scan of your uploaded code. Deleted after the scan.
sourcestringnoneOptional. A git URL (https:// or git@) to clone for white-box. Use this or upload_id.
budgetnumber10Per-scan spend cap, US dollars.
modestringstandardScan profile: quick, standard, or deep.
curl -X POST https://scan.millenniums.ai/api/scan \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target":"https://staging.yourapp.com/chat",
       "source":"https://github.com/acme/chat-app","budget":15}'
{ "run_id": "20260731-abc123" }
Returns before the scan finishes. You get a run_id immediately; poll GET /api/runs/<id> for progress and results.
GET/api/runs

List your scans, most recent first — each with id, status, running, and findings_count.

GET/api/runs/{run_id}

One scan in full, including the findings array (see Findings for every field). On free-tier plans the poc field is withheld. Returns 404 if the run isn't yours.

GET/api/runs/{run_id}/trace

The scan's chain-of-custody report — see Traceability. A timestamped timeline plus attestations proving your code stayed in an isolated sandbox, the sandbox was torn down, and (for uploads) the source was deleted. Every field is derived from a recorded event, not asserted.

{ "run_id": "20260731-abc123",
  "timeline": [
    { "ts": "2026-07-31T05:00:00Z", "event": "Isolated sandbox launched", … },
    { "ts": "2026-07-31T05:04:03Z", "event": "Sandbox torn down", "detail": "verified" },
    { "ts": "2026-07-31T05:04:04Z", "event": "Uploaded source deleted", … } ],
  "attestations": { "isolated_sandbox": true, "sandbox_torn_down": true,
    "uploaded_source_deleted": true, "no_model_training": true } }
FieldTypeMeaning
idstringThe run ID.
statusstringrunning, completed, stopped: token cap, …
runningbooltrue until the scan finishes.
findings_countintHow many proven findings.
findingsarrayThe findings (full detail on ?full / single-run fetch).

API — Targets

Saved, named targets you scan repeatedly. These are what the CI plans scan on each pull request.

GET/api/targets

List your registered targets.

POST/api/targets
BodyTypeDefaultNotes
targetstringRequired. The URL / repo / API.
namestring= targetA friendly label.
sourcestringnoneSource for white-box scans.
budgetnumber10Per-scan spend cap.
modestringstandardScan profile.

API — Domain verification

Prove you own a domain before scanning it — see Authorization & verification. Scanning a public target you haven't verified returns 403 with the token and instructions.

POST/api/domains/verify

Returns your per-domain token and re-checks ownership (meta tag, then DNS TXT). Pass attest: true to record the authorization certification. Call it once to get the token, add the tag/record, then call it again to confirm.

BodyTypeNotes
domainstringRequired. The domain (or a URL — we use its root).
attestboolCertify you own it / are authorized to test it.
{ "domain": "acme.com", "verified": true, "method": "meta",
  "meta_tag": "<meta name=\"millenniums-verification\" content=\"…\">",
  "dns_txt": "millenniums-verification=…" }
GET/api/domains

List your domains and their verification status.

API — PR reviews

GET/api/pr-reviews

The status of pull-request scans for your registered targets (Developer plan and up). Each entry reports the PR, whether the scoped scan is clean, and any proven net-new findings that blocked the merge. See the CI guide.

API — Chat

POST/api/chat

Ask questions in natural language. Pass a run_id to ground the answer in a specific scan.

BodyTypeRequiredNotes
messagesarrayyesChat turns, e.g. [{"role":"user","content":"…"}].
run_idstringnoGrounds the reply in that scan's findings.
{ "reply": "The prompt-injection finding on /chat lets a user…" }

API — Billing

POST/api/billing/checkout

Start a Stripe Checkout session to upgrade. Returns a hosted checkout url to redirect the user to.

BodyTypeNotes
planstringstarter · developer · team. Enterprise is sales-led.
{ "url": "https://checkout.stripe.com/c/pay/cs_live_…" }

Returns 501 if billing isn't configured on the instance, 400 for an unknown plan.

API — Errors & status codes

Errors return a JSON body { "error": "…" } with one of these statuses:

CodeMeaningCommon cause
400Bad requestMissing target, invalid email, unknown plan.
401UnauthorizedMissing or unknown bearer token.
402Payment requiredScan quota reached — upgrade or add a card. Body includes plan, quota, used.
403ForbiddenEmail not verified, or account suspended.
404Not foundUnknown route, or a run that isn't yours.
429Too many requestsSignup rate limit, or your plan's concurrent-scan cap.
500Server errorScan couldn't start (Docker / model key), or chat failed.
501Not implementedBilling not configured on this instance.
Need a hand? Email support@millenniums.ai, or open the app and use in-app chat.