← Semgrep Desk / API
Your token

Driving Semgrep Desk from your own code

Everything the web page does is one HTTP API. The base URL is https://api.skillsafe.ai/v1/app-api, every call carries Authorization: Bearer <token>, and every response is the same envelope.

The task field comes first

Semgrep Desk is one app with four lanes over the same rule. Every request must carry a task field; it is what routes the run. The four values are:

task What you get Derived from
auditThe rule audit: an operator inventory, findings, an ordered edit plan, and the corrected rule file.@trailofbits/semgrep-rule-creator
fp-triageThe false-positive triage: a per-line verdict on the annotated sample, the narrowest exclusions, and the rule with them applied.@trailofbits/fp-check
variantsThe bypass variants: a vector table with caught-now / caught-after columns, and the extended rule.@trailofbits/semgrep-rule-variant-creator
huntThe variant hunt: search targets with real queries, and a Markdown hunting plan.@trailofbits/variant-analysis

If task is missing or unrecognised the model answers the closest lane and names its choice in lane and in the first sentence of summary - it never blends two lanes into one answer. Read lane off the reply rather than assuming it echoes what you sent.

The envelope

Every response is {"ok": true, "data": {...}} or {"ok": false, "error": {"code": "...", "message": "..."}}. Read data; on a non-2xx status read error.code.

HTTP error.code What it means
400VALIDATION_ERRORThe body was not valid JSON, or a required field was missing.
401UNAUTHENTICATEDNo token, or a token that has expired. Mint a new guest token, or sign in.
402INSUFFICIENT_CREDITSThe balance is below min_credits. Call /estimate first and compare against /me.
404NOT_FOUNDWrong path. Every path on this page is exactly as written — note that only /guest names the app slug, and it does so in the body, never in the URL.
409IDEMPOTENCY_CONFLICTThe same Idempotency-Key was reused with a different body.
429RATE_LIMITEDBack off and retry. Never tight-loop.
503UPSTREAM_UNAVAILABLEThe model provider is unreachable. Retry with backoff.

Step 1 - get a token

A guest token is minted per app and needs no account; it is enough for /me and /estimate, and for a run only if the publisher sponsors guests (this app does not). For a real run, sign in on the token page and copy the token from there. This is the only endpoint that names the app slug, and it does so in the body, never in the URL. Every snippet below reuses the small client helper defined here. Keep your token out of source control - read it from a secrets manager or an environment variable rather than pasting it in, and treat every YOUR_TOKEN placeholder on this page as a reminder to do so.

Step 2 - check the balance

GET /me returns the subject and its credit balance. Compare it against hold_credits from step 3 before you submit a run: a 402 after submitting is a failure of your client, not of the service.

Step 3 - price the run (free)

POST /estimate costs nothing, creates no job, and returns model, model_alias, markup_bps, hold_credits, min_credits and sponsor_enabled. The hold prices the full output cap; the charge is usually far lower. Estimate the lane you are about to run - the hold differs per lane because the prompts and output caps differ.

The input fields

Field Type Meaning
taskstring, requiredOne of audit, fp-triage, variants, hunt. Routes the run. If it is missing or unrecognised the model answers the closest lane and names its choice in lane.
rulestring, requiredThe Semgrep rule file. The web page sends a digest: the rule YAML in a fenced block, the sample in a second block, and a short inventory of what its browser-side reader already established. Sending the raw YAML alone works too.
samplestringCode the rule is meant to fire on. May carry Semgrep test annotations (ruleid, ok, todoruleid, todook). The web page folds this into rule; either field is read.
intentstringOne or two sentences on what the rule is supposed to catch. This is what the audit lane measures the rule against.
targetstringpr-blocking, ci-advisory, local-dev or registry.
tolerancestringnoise-averse, balanced or miss-averse. Changes the recall/precision advice.
contextstringAnything else worth knowing: frameworks, existing wrappers, repository size.
prescan_factsobjectWhat a reader already established. risk_tier, flags[] (id, severity, rule, detail), rules[], annotations[], stats. Every flag id you send here must come back exactly once in coverage_check. Optional — omit it and the model reads the rule unaided.
upstreamstringThe previous lane's output, when you are chaining lanes.
retry_notestringOptional. Sent only when a previous reply failed to parse, to tell the model exactly what to correct. The prompt is instructed to obey it. Reuse the same Idempotency-Key as the attempt it is retrying, or the retry is billed as a second run.
samplestringAlways sent as the empty string by the web app, which folds the code sample into rule under a ## The code sample heading. You may send it either way; the prompt reads the sample from rule and its parsed annotations from prescan_facts.annotations[].

Step 4 - run it, and poll

POST /run returns a job_id immediately. Always send an Idempotency-Key: a retried request with the same key returns the same job instead of billing twice. The web page derives its key from the lane plus a hash of the input plus an attempt counter, which is why two lanes over the same rule are two distinct runs.

Then poll GET /jobs/{job_id} until status is succeeded or failed. The reply text is at output.output, as a JSON string you parse a second time.

Step 5 - or stream it

POST /run-stream is the same call over Server-Sent Events. Frames are event: delta with {"text": "..."}, then one event: done carrying {job_id, status, charged_credits, output}. On an idempotent replay the server answers with plain JSON rather than an event stream, so check the response Content-Type before you start parsing frames. The same Idempotency-Key rule applies.

The output contract

The reply is one JSON object. It is identical in shape across all four lanes; only the inner meaning of steps, detail_table and artifact changes.

Field Type Meaning
lanestringThe lane that was answered.
titlestringShort title naming the rule under review.
headlinestringOne sentence: the single most important thing.
verdictstringship, tighten or rewrite. Any other value is coerced to tighten.
confidencestringhigh, medium or low.
summarystringTwo to four sentences of prose.
findings[]arrayid, severity (critical/high/medium/low), rule, operator, problem, evidence, fix. An entry with no problem is dropped by the renderer.
steps[]arrayn, phase, action, owner, signal. An entry with no action is dropped. Renumbered from 1.
detail_tableobjecttitle, columns[], rows[][]. Rows are padded or truncated to the column count. The columns differ per lane — see the worked examples below.
artifactobjecttitle, filename, language (yaml or markdown), content. For the three YAML lanes the content is a complete loadable rule file starting with rules:.
coverage_check[]arrayflag, status (confirmed/cleared/not-applicable), note. One entry per prescan flag id you sent.
assumptions[]arrayStrings. May be empty.
open_questions[]arrayStrings. May be empty.

One worked example per lane

The only field that changes between these is task. What comes back differs in the three per-lane sections below.

Rule audit task: "audit"

The operator inventory, the findings, the ordered edit plan and the corrected rule.

False-positive triage task: "fp-triage"

A verdict per annotated line, and the narrowest exclusions applied to a whole rule file.

Bypass variants task: "variants"

The vectors the rule walks past, with honest caught-now and caught-after columns.

Variant hunt task: "hunt"

Search targets with real queries, and a Markdown plan a person executes against the repository.

Chaining lanes

The web page offers each lane's result to the next as a button. From your own code, put a digest of the previous reply in upstream and, when that lane produced a loadable rule file, send artifact.content as the next call's rule. The pipeline is audit → fp-triage → variants → hunt.

What this API will not do

It does not execute Semgrep, does not read your repository, and does not verify that a generated rule matches anything. It reads the text you send. Every generated YAML artifact should be run through semgrep --validate and your own test corpus before it is merged - the web page re-parses each artifact and warns when it would not load, and your client should do the same.