The Scan API runs STACK's prompt-injection detector against arbitrary content you supply. Use it on retrieved data — emails, documents, web pages, calendar invites, API responses, anything an agent might pull from a non-user source — BEFORE feeding the content into your LLM. Returns a verdict so your agent can refuse to proceed if the content carries an injection payload.
Most production prompt injections are indirect — the user is benign, the agent retrieves a doc/email, and the doc/email contains the injection.POST /v1/scan exists specifically for this attack class. Available on every tier. Scan calls use the shared monthly action allowance.
Send the content to scan. The detector runs the same three-layer chain that fires on/v1/proxy and /v1/skills/:id/invoke:
curl -X POST https://api.getstack.run/v1/scan \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"content": "<the email body, document text, or scraped webpage>",
"context": "email",
"source": "sender@external.com"
}'{
"verdict": "critical",
"scan_id": "ses_abc123def456",
"duration_ms": 2120,
"match": {
"pattern_id": "override_ignore_previous",
"severity": "critical",
"field_path": "content",
"matched_excerpt": "ignore all previous instructions",
"encoding": "base64_decode"
},
"llm": {
"verdict": "critical",
"confidence": 92,
"reasoning": "Authority impersonation attempt with embedded directive...",
"model": "anthropic/claude-haiku-4.5"
},
"l3_degraded": null
}When L2 normalization reveals a match the raw text didn't expose, the response carries an encoding field on the match. Possible values:
// Pseudo-code: scan retrieved content before feeding to the LLM
async function summarizeEmail(emailBody, sender) {
const result = await stack.scan.scan({
content: emailBody,
context: 'email',
source: sender,
}, { passportToken: passport });
if (result.verdict === 'critical') {
// Refuse — the email contains injection. Surface to the user.
return { error: 'Email body contains a prompt-injection payload; refusing to summarize.', match: result.match };
}
if (result.verdict === 'suspicious') {
// Warn but proceed with extra caution. Optionally lower trust.
log.warn('scan suspicious', result.match);
}
return await llm.summarize(emailBody);
}Scan calls use the account's shared monthly action allowance:
Above the allowance, each call debits $0.0001 from the Wallet. A request returns 402 Payment Required when the Wallet cannot cover it.
curl https://api.getstack.run/v1/scan/usage \
-H "Authorization: Bearer sk_live_your_key"{
"tier": "developer",
"limit": 250000,
"used": 1523,
"remaining": 248477,
"period": "2026-04"
}Every match (warning or critical) records a prompt_injectionsecurity event with:
The event is severity-tagged exactly the same as proxy/skill scans: warning for soft phrasing, critical for strong instruction-override or jailbreak-name matches.
Benchmark numbers on a 1087-sample corpus (deepset/prompt-injections + STACK curated supplement + AgentDojo extracted): full L1+L2+L3 chain achieves F1 0.87, precision 0.98, recall 0.79 (locked 2026-05-25). L1 alone reaches F1 0.43; L1+L2 reaches 0.49. L3 is the largest single contributor.
The detector is strong but not infallible. Bear in mind: (1) L3 sees the content you scan — it leaves STACK's infrastructure to reach the OpenRouter-hosted model. Do not submit content that you cannot send to this processor. (2) Adversarial attacks targeted specifically at LLM classifiers (e.g. carefully-crafted confusing-the-classifier prompts) may evade L3. (3) Indirect attacks that don't carry any directive surface in the content (e.g. data poisoning that only manifests later) are out of scope for input-side scanning. Use the scan verdict as one strong signal in your overall trust model, not as a complete guarantee.