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

# Campaigns reference

> Data model, operations, filters, and permissions for Campaigns in Sift

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

A Campaign groups Reports from multiple Runs into a single workspace for coordinated review. This page covers the Campaign data model, supported operations, list filters, and access control behavior.

## Data model

### Campaign fields

<MintTable
  columns={['Field', 'Type', 'Description']}
  rows={[
['campaign_id', 'string', 'Unique identifier for the Campaign.'],
['name', 'string', 'Display name of the Campaign.'],
['description', 'string (optional)', 'A description of the Campaign scope or purpose.'],
['client_key', 'string (optional)', 'User-specified unique identifier. Useful for referencing a Campaign by an external key.'],
['organization_id', 'string', 'The organization this Campaign belongs to.'],
['reports', 'list of CampaignReport', 'The Reports included in this Campaign. Each entry includes rule and annotation summary counts.'],
['tags', 'list of TagRef', 'Tags applied to the Campaign.'],
['metadata', 'list of MetadataValue', 'User-defined metadata key-value pairs.'],
['is_archived', 'bool', 'Whether the Campaign is archived. Inferred from whether archived_date is set.'],
['archived_date', 'timestamp', 'The date the Campaign was archived, if applicable.'],
['created_from_campaign_id', 'string (optional)', 'If this Campaign was created by duplicating another, the source Campaign ID is stored here.'],
['created_by_user_id', 'string', 'The user who created the Campaign.'],
['modified_by_user_id', 'string', 'The user who last modified the Campaign.'],
['created_date', 'timestamp', 'When the Campaign was created.'],
['modified_date', 'timestamp', 'When the Campaign was last modified.'],
['reports_include_summaries', 'bool', 'Indicates whether the summary fields on each CampaignReport are populated. When false, use Get Campaign Report Summaries to fetch counts separately.']
]}
/>

### CampaignReport fields

Each Report in a Campaign exposes a summary of its rule evaluation results.

<MintTable
  columns={['Field', 'Type', 'Description']}
  rows={[
['report_id', 'string', 'The ID of the Report.'],
['report_name', 'string', 'The display name of the Report.'],
['num_annotations', 'uint32', 'Total number of Annotations across all Rules in this Report.'],
['num_passed_rules', 'uint32', 'Rules that were never triggered and generated no Annotations.'],
['num_accepted_rules', 'uint32', 'Rules whose Annotations are all marked Accepted.'],
['num_failed_rules', 'uint32', 'Rules with at least one Annotation marked Failed.'],
['num_open_rules', 'uint32', 'Rules with some Annotations still Open and none marked Failed.']
]}
/>

## List Campaigns filters

Use a [CEL expression](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions) with the `filter` parameter when listing Campaigns.

<MintTable
  columns={['Field', 'Description']}
  rows={[
['campaign_id', 'Match by Campaign ID.'],
['name', 'Match by Campaign name.'],
['description', 'Match by Campaign description.'],
['client_key', 'Match by user-specified external key.'],
['run_id', 'Return Campaigns that include a Report generated from the specified Run.'],
['report_id', 'Return Campaigns that include the specified Report.'],
['report_name', 'Match by the name of an included Report.'],
['tag_id', 'Match by tag ID.'],
['tag_name', 'Match by tag name.'],
['created_by_user_id', 'Match by the user who created the Campaign.'],
['is_archived', 'Filter to archived or active Campaigns. Defaults to false (active only) unless include_archived is set.'],
['metadata', 'Match by user-defined metadata key-value pairs.']
]}
/>

Campaigns can be ordered by `name`, `created_date`, or `modified_date`. The default order is `created_date` descending (newest first).

## List Campaign Annotations filters

Use a CEL expression with the `filter` parameter when listing Annotations within a Campaign.

<MintTable
  columns={['Field', 'Description']}
  rows={[
['annotation_id', 'Match by Annotation ID.'],
['name', 'Match by Annotation name.'],
['description', 'Match by Annotation description.'],
['state', 'Filter by status: Open, Failed, or Accepted.'],
['assignee', 'Filter by the assigned reviewer.'],
['run_id', 'Filter to Annotations from a specific Run.'],
['report_id', 'Filter to Annotations from a specific Report.'],
['asset_id', 'Filter by Asset ID.'],
['asset_name', 'Filter by Asset name.'],
['annotation_type', 'Filter by Annotation type.'],
['tag_name', 'Filter by tag name.'],
['pending', 'Filter to Annotations that are pending review.'],
['created_by_user_id', 'Match by the user who created the Annotation.'],
['created_by_rule_condition_version_id', 'Match by the Rule condition version that generated the Annotation.'],
['start_time', 'Filter by Annotation start time.'],
['end_time', 'Filter by Annotation end time.'],
['created_date', 'Filter by creation date.'],
['modified_date', 'Filter by last modified date.'],
['metadata', 'Match by user-defined metadata key-value pairs.']
]}
/>

Annotations can be ordered by `created_date`, `modified_date`, `start_time`, or `end_time`. The default order is `created_date` ascending (oldest first).

## Permissions

<MintTable
  columns={['Action', 'Admin', 'Member', 'Viewer', 'Guest']}
  rows={[
['Create Campaign', '✓', '✓', '', ''],
['Edit Campaign', '✓', '✓', '', ''],
['View Campaign', '✓', '✓', '✓', '✓']
]}
/>

## Metadata

Campaigns support user-defined metadata. Metadata is searchable and can be used to filter Campaigns in lists. See [Organize resources with Metadata](/documentation/manage/organize-and-filter-resources-with-metadata) for steps on adding metadata to a Campaign.

## Related workflows

* [Campaigns: track a multi-Run review effort](/documentation/review/track-a-multi-run-review-campaign)
* [Detect and review issues in a Run](/documentation/review/detect-and-review-issues-in-a-run)
* [Triage and close out flagged issues](/documentation/review/triage-and-close-out-flagged-issues)
