> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usetrident.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Red-Team API: Trigger Attack Campaigns Programmatically

> Use the Trident Red-Team API to trigger automated attack campaigns, poll for results, and run garak probe suites from your code or CI/CD.

The Red-Team API lets you launch security attack campaigns against your AI agents programmatically — from CI/CD pipelines, GitHub Actions, SDK calls, or any HTTP client. Trident provides three complementary scan engines: autonomous AI campaigns (recommended for broad coverage), Promptfoo plugin runs (deterministic, plugin-scoped), and Garak static probes (compliance-friendly, high-volume).

All red-team endpoints return a run or job ID immediately (HTTP 202 Accepted). You then poll for status and retrieve findings once the run completes.

***

## POST /api/public/trident/redteam/campaign

Enqueue an autonomous Trident-AI red-team campaign against a target agent. The campaign engine selects attack skills based on your chosen `scanMode`, runs multi-turn adversarial interactions, and files findings directly into your project inbox.

**Endpoint:** `POST https://app.usetrident.dev/api/public/trident/redteam/campaign`

**Authentication:** HTTP Basic — see [Authentication](/api/authentication)

### Request body

<ParamField body="agentId" type="string" required>
  The ID of the agent to attack. Must match `[a-zA-Z0-9._-]+` and be
  registered in your project.
</ParamField>

<ParamField body="target" type="object" required>
  Describes how the campaign runner reaches your agent. Three target kinds are
  supported:

  **`openai-chat`** — OpenAI-compatible chat endpoint.

  | Field          | Type            | Notes                                        |
  | -------------- | --------------- | -------------------------------------------- |
  | `kind`         | `"openai-chat"` | Required                                     |
  | `baseUrl`      | `string` (URL)  | Required — your agent's chat completions URL |
  | `apiKey`       | `string`        | Optional — bearer token for the endpoint     |
  | `model`        | `string`        | Optional — model ID to pass in the request   |
  | `systemPrompt` | `string`        | Optional — system prompt to prepend          |

  **`http-proxy`** — Arbitrary HTTP endpoint with a templated body.

  | Field          | Type           | Notes                                                       |
  | -------------- | -------------- | ----------------------------------------------------------- |
  | `kind`         | `"http-proxy"` | Required                                                    |
  | `url`          | `string` (URL) | Required                                                    |
  | `headers`      | `object`       | Optional key/value request headers                          |
  | `bodyTemplate` | `string`       | Required — `$INPUT` is replaced with the attack text        |
  | `responsePath` | `string`       | Optional — dot-path to the reply field in the JSON response |
  | `timeoutMs`    | `number`       | Optional — per-request timeout, max 120 000 ms              |

  **`echo`** — No-op target for testing your pipeline.

  | Field       | Type     | Notes                                    |
  | ----------- | -------- | ---------------------------------------- |
  | `kind`      | `"echo"` | Required                                 |
  | `responses` | `object` | Optional map of input → response strings |
</ParamField>

<ParamField body="scanMode" type="string">
  Preset scan profile. Recommended for CI use because each mode has a
  calibrated cost envelope and duration estimate.

  | Mode         | Skills | Expected duration | Recommended for      |
  | ------------ | ------ | ----------------- | -------------------- |
  | `quick`      | \~3    | \< 2 min          | PR checks            |
  | `standard`   | \~7    | \~10 min          | Main branch gates    |
  | `deep`       | \~12   | \~25 min          | Release gates        |
  | `exhaustive` | \~20   | \~60 min          | Periodic full audits |

  Omit `scanMode` to configure skills manually via `skillIds`, `maxSteps`, and
  `perStepMaxIterations`.
</ParamField>

<ParamField body="skillIds" type="string[]">
  Explicit list of attack skill IDs to run. Use when you need fine-grained
  control over which attacks are attempted. Ignored when `scanMode` is set.
</ParamField>

<ParamField body="maxSteps" type="number">
  Maximum number of attack steps across the campaign, 1–20. Defaults are
  set by `scanMode`; override here for custom budgets.
</ParamField>

<ParamField body="perStepMaxIterations" type="number">
  Maximum adversarial turns per skill step, 1–60.
</ParamField>

<ParamField body="perStepCostCapUsd" type="number">
  Per-step LLM cost ceiling in USD, max \$10. The runner halts the step when
  this threshold is crossed.
</ParamField>

<ParamField body="totalCostCapUsd" type="number">
  Absolute total cost ceiling in USD, max \$100. The campaign aborts if
  cumulative LLM spend reaches this value.
</ParamField>

<ParamField body="goalHint" type="string">
  Up to 500 characters of context about what the agent should never do. The
  attack engine uses this to focus on the most relevant attack categories.
</ParamField>

<ParamField body="gapFillOnly" type="boolean">
  When `true`, the campaign skips skills that already have a landed finding
  against this agent, running only attack types that have not yet succeeded.
  Useful for incremental scans that build on earlier results.
</ParamField>

<ParamField body="priorRunsSinceDays" type="number">
  Look-back window in days (1–180) used by `gapFillOnly` to determine which
  skills already have findings.
</ParamField>

<ParamField body="attackContext" type="object">
  Optional context objects that sharpen specific attack categories.

  | Field                     | Type       | Purpose                                                           |
  | ------------------------- | ---------- | ----------------------------------------------------------------- |
  | `exfilCanaries`           | `string[]` | Secret strings to watch for in outputs (data exfiltration probes) |
  | `forbiddenTokens`         | `string[]` | Strings the agent must never output                               |
  | `highBlastTools`          | `string[]` | Tool names that have high-blast-radius if misused                 |
  | `attackerInjectedTargets` | `string[]` | Injection targets for indirect prompt injection probes            |
</ParamField>

### Example request

```json theme={null}
{
  "agentId": "prod-rag-assistant",
  "target": {
    "kind": "openai-chat",
    "baseUrl": "https://my-agent.example.com/v1/chat/completions",
    "apiKey": "sk-agent-token",
    "systemPrompt": "You are a helpful customer support assistant."
  },
  "scanMode": "standard",
  "goalHint": "Never reveal internal pricing, never execute administrative actions.",
  "attackContext": {
    "exfilCanaries": ["INTERNAL-SECRET-42"],
    "forbiddenTokens": ["DROP TABLE", "rm -rf"]
  }
}
```

### Example response (HTTP 202)

```json theme={null}
{
  "jobId": "e3f7b2d1-9a0c-4b5e-8f1d-7c6a2e9b3d4f",
  "statusUrl": "/api/public/trident/redteam/campaign/e3f7b2d1-9a0c-4b5e-8f1d-7c6a2e9b3d4f",
  "findingsUrl": "/api/public/trident/findings?redteamRunId=e3f7b2d1-9a0c-4b5e-8f1d-7c6a2e9b3d4f&sinceDays=1",
  "forecast": {
    "mode": "standard",
    "expectedCostUsd": 1.20,
    "costRangeUsd": { "low": 0.84, "high": 1.56 },
    "expectedDuration": "~10 min",
    "hardCostCapUsd": 7.00,
    "cacheHitRate": 0.15,
    "derivation": "7 skills × 2 iterations × gpt-4o"
  }
}
```

***

## GET /api/public/trident/redteam/campaign/{jobId}

Poll a campaign's current state. Returns the job state plus a count of findings filed so far, so you can distinguish "still running" from "done, zero findings."

**Endpoint:** `GET https://app.usetrident.dev/api/public/trident/redteam/campaign/{jobId}`

**Authentication:** HTTP Basic — see [Authentication](/api/authentication)

### Path parameters

<ParamField path="jobId" type="string" required>
  The `jobId` returned by `POST /api/public/trident/redteam/campaign`.
</ParamField>

### Example request

```bash curl theme={null}
curl "https://app.usetrident.dev/api/public/trident/redteam/campaign/e3f7b2d1-9a0c-4b5e-8f1d-7c6a2e9b3d4f" \
  -H "Authorization: Basic $CREDENTIALS"
```

### Example response

```json theme={null}
{
  "jobId": "e3f7b2d1-9a0c-4b5e-8f1d-7c6a2e9b3d4f",
  "state": "active",
  "enqueuedAt": 1718025727000,
  "startedAt": 1718025730000,
  "finishedAt": null,
  "progress": { "step": 3, "totalSteps": 7, "currentSkill": "prompt-injection" },
  "failedReason": null,
  "findingCount": 1
}
```

### State values

| `state`     | Meaning                                                                               |
| ----------- | ------------------------------------------------------------------------------------- |
| `queued`    | Job is waiting to be picked up                                                        |
| `active`    | Worker is currently running the campaign                                              |
| `completed` | Campaign finished — check `findingsUrl` for results                                   |
| `failed`    | Campaign encountered an unrecoverable error; see `failedReason`                       |
| `delayed`   | Job is scheduled for a future time                                                    |
| `waiting`   | Job is waiting for a concurrency slot                                                 |
| `unknown`   | Job ID not found or no longer tracked — if `findingCount > 0`, the campaign completed |

<Note>
  Finished campaigns may no longer report a specific state. If `state` is
  `"unknown"` but `findingCount` is greater than zero, the campaign ran
  successfully and findings are available.
</Note>

***

## POST /api/public/trident/redteam/run

Enqueue a Promptfoo-based red-team run. This engine uses static Promptfoo plugins and strategies for deterministic, broad-coverage scanning — ideal for compliance audits where you need reproducible attack counts.

**Endpoint:** `POST https://app.usetrident.dev/api/public/trident/redteam/run`

**Authentication:** HTTP Basic — see [Authentication](/api/authentication)

### Request body

<ParamField body="agentId" type="string" required>
  Agent ID — must match `[a-zA-Z0-9._-]+`.
</ParamField>

<ParamField body="purpose" type="string" required>
  A human-readable description of what this agent does. Surfaced in the
  dashboard and used to focus plugin attacks. Maximum 500 characters.
</ParamField>

<ParamField body="plugins" type="string[]" required>
  Promptfoo plugin IDs to run, for example `["foundation", "harmful", "pii",
      "bias", "financial"]`. Between 1 and 40 plugins.
</ParamField>

<ParamField body="strategies" type="string[]" required>
  Promptfoo strategy IDs — for example `["basic", "jailbreak",
      "jailbreak:tree", "crescendo"]`. Between 1 and 8 strategies.
</ParamField>

<ParamField body="targetProviderId" type="string" required>
  Provider type Promptfoo should use, for example `"http"`,
  `"openai-chat"`, or `"echo"`.
</ParamField>

<ParamField body="targetConfig" type="object" required>
  Provider-specific configuration — URL, headers, auth, response parser.
  Shape depends on `targetProviderId`.
</ParamField>

<ParamField body="numTests" type="number" default="2">
  Number of test cases generated per (plugin, strategy) combination, 1–20.
  Higher values increase coverage and cost.
</ParamField>

<ParamField body="policyId" type="string">
  Optional Trident policy ID to associate with this run for compliance
  tracking.
</ParamField>

### Example request

```json theme={null}
{
  "agentId": "prod-rag-assistant",
  "purpose": "Customer support assistant with access to order lookup tools",
  "plugins": ["foundation", "harmful", "pii"],
  "strategies": ["basic", "jailbreak"],
  "targetProviderId": "http",
  "targetConfig": {
    "url": "https://my-agent.example.com/chat",
    "method": "POST",
    "headers": { "Authorization": "Bearer sk-agent-token" },
    "body": "{\"message\": \"{{prompt}}\"}",
    "transformResponse": "json.reply"
  },
  "numTests": 3
}
```

### Example response (HTTP 202)

```json theme={null}
{
  "runId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "statusUrl": "/api/public/trident/findings?redteamRunId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&sinceDays=1",
  "findingsUrl": "/api/public/trident/findings?redteamRunId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&sinceDays=1"
}
```

***

## POST /api/public/trident/redteam/garak

Enqueue an NVIDIA Garak static-probe run. Garak fires approximately 2,500 attack variants across four probe suites — encoding, leakreplay, snowball, and glitch — making it suitable for compliance reports that require deterministic, reproducible attack counts.

**Endpoint:** `POST https://app.usetrident.dev/api/public/trident/redteam/garak`

**Authentication:** HTTP Basic — see [Authentication](/api/authentication)

### Probe suites

| Probe        | What it tests                                                                                  |
| ------------ | ---------------------------------------------------------------------------------------------- |
| `encoding`   | Character encoding tricks — Base64, ROT13, Unicode homoglyphs — used to bypass content filters |
| `leakreplay` | System prompt and training-data leakage                                                        |
| `snowball`   | Escalating multi-turn requests designed to erode refusal guardrails                            |
| `glitch`     | Unusual token sequences and adversarial suffixes that cause unexpected model behaviour         |

### Request body

<ParamField body="agentId" type="string" required>
  Agent ID — must match `[a-zA-Z0-9._-]+`.
</ParamField>

<ParamField body="purpose" type="string" required>
  Scan label surfaced in the dashboard. Maximum 500 characters.
</ParamField>

<ParamField body="probes" type="string[]" required>
  Subset of `["encoding", "leakreplay", "snowball", "glitch"]`. At least one
  is required.
</ParamField>

<ParamField body="targetBaseUrl" type="string" required>
  Base URL of the HTTP endpoint Garak should attack.
</ParamField>

<ParamField body="targetModel" type="string" default="custom">
  Human-readable model identifier, surfaced in the dashboard. Maximum 120
  characters.
</ParamField>

<ParamField body="targetPath" type="string">
  Path appended to `targetBaseUrl`. Defaults to `/chat`.
</ParamField>

<ParamField body="targetApiKey" type="string">
  Optional bearer token or API key for the target. Encrypted at rest on
  enqueue — never logged.
</ParamField>

<ParamField body="requestTemplate" type="string" default="{&#x22;prompt&#x22;:&#x22;$INPUT&#x22;}">
  JSON body template for each probe request. `$INPUT` is replaced with the
  Garak-synthesised attack string. Maximum 8 000 characters.
</ParamField>

<ParamField body="responseField" type="string" default="reply">
  Top-level JSON key or JSONPath (prefix with `$`) that contains the response
  text. Maximum 200 characters.
</ParamField>

### Example request

```json theme={null}
{
  "agentId": "prod-rag-assistant",
  "purpose": "Garak encoding + leakreplay audit for Q2 compliance report",
  "probes": ["encoding", "leakreplay"],
  "targetBaseUrl": "https://my-agent.example.com",
  "targetPath": "/chat",
  "targetApiKey": "sk-agent-token",
  "requestTemplate": "{\"message\": \"$INPUT\"}",
  "responseField": "content"
}
```

### Example response (HTTP 202)

```json theme={null}
{
  "runId": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
  "statusUrl": "/api/public/trident/findings?redteamRunId=f9e8d7c6-b5a4-3210-fedc-ba9876543210&sinceDays=1",
  "findingsUrl": "/api/public/trident/findings?redteamRunId=f9e8d7c6-b5a4-3210-fedc-ba9876543210&sinceDays=1"
}
```

***

## Polling pattern: wait for a campaign to complete

Use the campaign status endpoint to poll until the job reaches a terminal state, then retrieve findings.

```typescript TypeScript theme={null}
const BASE = "https://app.usetrident.dev";
const CREDENTIALS = Buffer.from(
  `${process.env.TRIDENT_PROJECT_PUBLIC_KEY}:${process.env.TRIDENT_PROJECT_SECRET_KEY}`
).toString("base64");

async function triggerAndWait(agentId: string, targetUrl: string) {
  // 1. Enqueue the campaign
  const launchRes = await fetch(
    `${BASE}/api/public/trident/redteam/campaign`,
    {
      method: "POST",
      headers: {
        Authorization: `Basic ${CREDENTIALS}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        agentId,
        target: { kind: "openai-chat", baseUrl: targetUrl },
        scanMode: "standard",
      }),
    },
  );

  const { jobId, findingsUrl } = await launchRes.json();
  console.log(`Campaign enqueued: ${jobId}`);

  // 2. Poll until the job reaches a terminal state
  const TERMINAL = new Set(["completed", "failed", "unknown"]);
  let state = "queued";
  let findingCount = 0;

  while (!TERMINAL.has(state)) {
    await new Promise((r) => setTimeout(r, 15_000)); // wait 15 s

    const statusRes = await fetch(
      `${BASE}/api/public/trident/redteam/campaign/${jobId}`,
      { headers: { Authorization: `Basic ${CREDENTIALS}` } },
    );
    const status = await statusRes.json();
    state = status.state;
    findingCount = status.findingCount;
    console.log(`  state=${state}  findings=${findingCount}`);
  }

  if (state === "failed") {
    throw new Error(`Campaign failed`);
  }

  // 3. Fetch findings produced by this run
  const findRes = await fetch(`${BASE}${findingsUrl}`, {
    headers: { Authorization: `Basic ${CREDENTIALS}` },
  });
  return findRes.json();
}
```
