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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.endorlabs.com/feedback

```json
{
  "path": "/developers-api/rest-api/using-the-rest-api/data-model/common-fields/index",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Common fields

> Learn about the common fields for all objects in the Endor Labs data model.

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    return processText(text);
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <thead>
        <tr>
          {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={rowKey(row)}>
            {columns.map(col => <td key={col}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

All objects adhere to the same high-level structure as outlined below. Object specific fields are defined in **Spec**. For more information, see [Resource kinds](/developers-api/rest-api/using-the-rest-api/data-model/resource-kinds).

## UUID

All objects have a unique **UUID**. You can use UUID to retrieve objects individually through the API.

## Meta

All objects include a common nested object called **Meta**.

Meta is a mandatory object that contains the common fields for each object, including the following fields.

<YamlTable>
  {`


    - Field_name: \`name\`
    Description: The name of the object.
    - Field_name: \`description\`
    Description: A description of the object.
    - Field_name: \`kind\`
    Description: The resource kind of the object (for example, \`RepositoryVersion\`).
    - Field_name: \`version\`
    Description: The version of the object, used to differentiate between different versions if needed.
    - Field_name: \`parent_uuid\`
    Description: The UUID of the parent object.
    - Field_name: \`parent_kind\`
    Description: The resource kind of the parent object (for example, \`Project\`).
    - Field_name: \`create_time\`
    Description: The time the object was created.
    - Field_name: \`update_time\`
    Description: The time the object was last updated (HTTP PATCH).
    - Field_name: \`upsert_time\`
    Description: The time the object was last updated or created (HTTP POST).
    - Field_name: \`created_by\`
    Description: The name and authentication source of the user who created the object. Example: \`ewok@endor.ai@google@api-key\`
    - Field_name: \`updated_by\`
    Description: The name and authentication source of the last user who updated the object. Example: \`vulnerabilityingestor@endor.ai@x509\`
    - Field_name: \`tags\`
    Description: A list of custom tags attached to the object. Tags can be used to organize objects and find collections of objects that satisfy certain conditions. A tag must be 255 characters or fewer. Tags may contain alphanumeric characters, \`@\`, \`_\`, \`.\`, and \`-\`. An optional prefix must be separated with \`=\` (for example, \`my_tag=my_value\`).
    - Field_name: \`annotations\`
    Description: Map of additional metadata for the object. Annotation keys may contain alphanumeric characters, \`_\`, \`.\`, and \`-\`. Annotation values can be structured or unstructured and may include characters not permitted by tags. Values must be 16384 bytes or smaller.

    `}
</YamlTable>

## TenantMeta

Most objects include a common nested object called **TenantMeta**.

TenantMeta contains the following field.

<YamlTable>
  {`


    - Field_name: \`namespace\`
    Description: Name of the namespace the object belongs to. Organizes organizational units into virtual groupings of objects. Namespaces must be fully qualified names (for example, the child namespace of \`endor.prod\` called \`app\` is \`endor.prod.app\`).

    `}
</YamlTable>

### OSS tenant

There is a common tenant for all OSS projects called `oss`, to which customers have read access.

## Spec

All objects include a common nested object called **Spec**. This mandatory object contains the specification of the object, representing its current state. For more information, see [Resource kinds](/developers-api/rest-api/using-the-rest-api/data-model/resource-kinds).

## Context

Most objects include a common nested object called **Context**. Contexts keep objects from different scans separated.

The context object has the following fields.

<YamlTable>
  {`


    - Field_name: \`type\`
    Description: The [type](#context-types) of context, defined based on how endorctl is being used.
    - Field_name: \`id\`
    Description: The ID of the context, such as a pull request ID or branch reference.
    - Field_name: \`will_be_deleted_at\`
    Description: The time when the object will be automatically deleted by the system.
    - Field_name: \`tags\`
    Description: A list of tags applied to a context. Used primarily for CI and SBOM contexts.

    `}
</YamlTable>

### Context types

Each context has a type and an id. For example, objects created during a scan of the default branch belong to the **main** context, while objects for non-default branches have the context type **ref**.

<YamlTable>
  {`


    - Context_type: \`CONTEXT_TYPE_MAIN\`
    Description: Objects from a scan of the default branch. All objects in the OSS namespace are in the main context. The context ID is always \`default\`.
    - Context_type: \`CONTEXT_TYPE_REF\`
    Description: Objects from a scan of a specific branch. The context ID is the branch reference name.
    - Context_type: \`CONTEXT_TYPE_CI_RUN\`
    Description: Objects from a PR scan. The context ID is the PR UUID. Objects in this context are deleted after three weeks.
    - Context_type: \`CONTEXT_TYPE_SBOM\`
    Description: Objects from an SBOM scan. The context ID is the SBOM serial number or some other unique identifier.
    - Context_type: \`CONTEXT_TYPE_EXTERNAL\`
    Description: Indicates that this object is a copy/temporary value of an object in another project. Used for same-tenant dependencies. In source code reference this is equivalent to "vendor" folders. Package versions in the external context are only scanned for call graphs. No other operations are performed on them.

    `}
</YamlTable>

## Processing status

**Project** and **PackageVersion** objects include a common nested object called **ProcessingStatus**, which contains fields about the processing status of the object (when it was/will be scanned). The processing status object has the following fields:

<YamlTable>
  {`


    - Field_name: \`scan_state\`
    Description: The [state](#scan-state) of the scan.
    - Field_name: \`scan_time\`
    Description: The last time the object was scanned. Projects onboarded via the GitHub App are scanned every 24 hours.
    - Field_name: \`analytic_time\`
    Description: The last time the object was analyzed. Analytics are run automatically every 24 hours.
    - Field_name: \`disable_automated_scan\`
    Description: A boolean used to disable automatic scanning by the system.

    `}
</YamlTable>

### Scan state

The following scan states are supported.

<YamlTable>
  {`


    - Scan_state: \`SCAN_STATE_NOT_PROCESSED\`
    Description: The object has not yet been processed by the system.
    - Scan_state: \`SCAN_STATE_IDLE\`
    Description: The object has been scanned at least once.
    - Scan_state: \`SCAN_STATE_INGESTING\`
    Description: The object is being scanned.
    - Scan_state: \`SCAN_STATE_ANALYTIC\`
    Description: The object is being analyzed.
    - Scan_state: \`SCAN_STATE_UNREACHABLE\`
    Description: The object is not reachable from the scheduler.
    - Scan_state: \`SCAN_STATE_REQUEST_FULL_RESCAN\`
    Description: The object is marked for a complete rescan. This only applies to OSS projects.
    - Scan_state: \`SCAN_STATE_REQUEST_INCREMENTAL_RESCAN\`
    Description: The object is marked for an incremental rescan, where only new packages discovered in the scan are added.

    `}
</YamlTable>

## Example

The following is an example of a **Project** object.

```json expandable theme={null}
{
  "meta": {
    "create_time": "2023-12-05T00:04:21.853Z",
    "kind": "Project",
    "name": "https://github.com/my_organization/my_repository.git",
    "update_time": "2024-05-01T16:50:03.830911988Z",
    "version": "v1"
  },
  "processing_status": {
    "analytic_time": "2024-05-01T16:45:06.483972413Z",
    "disable_automated_scan": true,
    "scan_state": "SCAN_STATE_IDLE",
    "scan_time": "2024-03-18T14:38:31.899249002Z"
  },
  "spec": {
    "git": {
      "full_name": "endorlabs/monorepo",
      "git_clone_url": "git@github.com:my_organization/my_repository.git",
      "http_clone_url": "https://github.com/my_organization/my_repository.git",
      "organization": "endorlabs",
      "path": "monorepo",
      "web_url": "https://api.github.com/my_organization/my_repository"
    },
    "internal_reference_key": "https://github.com/my_organization/my_repository.git",
    "platform_source": "PLATFORM_SOURCE_GITHUB"
  },
  "tenant_meta": {
    "namespace": "my_namespace"
  },
  "uuid": "656e69058032bf0abaaeb681"
}
```
