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

# Claim a case for investigation

> Takes ownership of a `discover` case. It moves to
`pending_investigation` and leaves the analysts' triage queue, exactly
as when an analyst escalates it in TOTAL. The request body is optional.

A case already claimed is left as it is (`claimed: false`), so a retry
is safe. A closed case returns `409`.




## OpenAPI

````yaml https://eris.platform.total.truu.ai/api/v1/external/docs post /cases/{case_id}/claim
openapi: 3.0.3
info:
  title: TOTAL Public API
  version: 3.0.0
  description: >
    The TOTAL API gives you programmatic access to your organization's

    security data. Use it to query cases, threat events, personas, and

    non-human identities, and to feed verdicts back into the platform.


    ## Authentication


    All routes in this specification are served under `/api/v1/external` and

    require a domain-scoped API key via the `X-API-Key` header. Generate and

    manage keys from the API Keys page in Settings.


    The admin console (Atlas) uses separate JWT-authenticated routes under

    `/api/v1/eris/...` (for example `GET /api/v1/eris/integrations`); those are

    not part of this public API surface.


    ## Scopes

    Each key carries scopes. A key never reaches further than the person who

    made it: it is granted only scopes its creator's TOTAL access allows, and

    on every request it keeps only those its creator can still grant. A key

    whose creator is deactivated or loses every granting role stops working

    (`401`); a route answers `403` when the key lacks the scope it needs.


    | Scope | Grants |

    |---|---|

    | `cases:read` | `GET /cases`; with `users:read`, `GET /users/{tuid}/cases`;
    the `security` block on `GET /users/{tuid}`; `case_status` on persona detail
    |

    | `cases:write` | `POST /cases/{case_id}/claim`, `POST
    /cases/{case_id}/verdict` |

    | `threats:read` | `GET /threats` |

    | `users:read` | `GET /users`, `GET /users/{tuid}`, `GET
    /users/{tuid}/cases` |

    | `watchlist:write` | `POST /watchlist` |

    | `personas:read` | every `/personas` and `/nhi` route |


    ## SOAR integration

    1. **Poll** `GET /cases?updated_after=<watermark>`. Reading never changes
       a case. Every change to a case moves its `updated_at`: being published
       to analysts, being claimed, being closed in TOTAL or through this API,
       an approved update to its findings. With `updated_after`, results come
       oldest change first; keep the last item's `updated_at` as the next
       watermark. The bound is inclusive and TOTAL also touches cases for its
       own bookkeeping, so the same case can come back unchanged: deduplicate
       by `case_id` and `updated_at`, and act on `case_state`. Add
       `state=discover` to see only cases waiting for triage.
    2. **Claim** `POST /cases/{case_id}/claim`. The case moves to
       `pending_investigation`, exactly as when an analyst escalates it in
       TOTAL, and leaves the analysts' triage queue. Claiming again changes
       nothing.
    3. **Work** the case: it already carries its threat events; `/users` and
       `/personas` add context. `/threats` is for looking events up, not for
       polling: a threat appears only once its case is published, so new
       threats are seen through `/cases`.
    4. **Close** it with `POST /cases/{case_id}/verdict`, or read the verdict
       an analyst closed it with in TOTAL from `case_state`, `close_reason`
       and `close_note`.

    Approved updates to a case never appear as a separate case: the case

    keeps its `case_id`. Cases are about a person (`identity_kind: human`) or

    a non-human identity (`identity_kind: nhi`, `tuid` holding its `nuid`).


    ## Pagination

    List routes use cursor pagination: `page_size` (default 50, max 200) and

    optional `next_cursor` from the previous page. Responses include

    `next_cursor` when more results are available. On case lists the cursor is

    an opaque string; elsewhere it is the id of the last item. A cursor is

    valid only with the same filters.


    ## Errors

    An error body is `{"error": "<what went wrong>"}`. A request body that

    fails validation answers `422` with `details`: where and why, never the

    values sent. An unexpected failure answers `500` with a `request_id` to

    quote to support.


    ## Versioning

    Breaking changes ship under a new path (`/api/v2/...`); `/api/v1` keeps

    its behaviour once released.


    ## Rate Limits

    Limits apply per API key. Read endpoints in this API are limited to 1,000

    requests/minute; write endpoints to 200 requests/minute.


    ## Availability

    Every route returns `423 Locked` with `error: not_available` until the

    TOTAL API is enabled for your domain. The `/nhi` routes also return it

    when your domain does not have non-human identities.


    ---


    ## Appendix A — Finding the source record behind a threat event


    Each threat event carries `source` (where it was found), `source_table`

    (the table, sourcetype or repository within that source) and

    `source_event_ids` (the records the finding cites, all from that one

    `source_table`). What an ID is, and how to look it up, depends on

    `source`:


    | `source` | `source_table` | Each `source_event_ids` value | Lookup |

    |---|---|---|---|

    | `azure_log_analytics` | Log Analytics table, e.g. `SigninLogs` | the row's
    `_ItemId` | `<source_table> | where _ItemId == "<id>"` in your workspace |

    | `splunk` | sourcetype, or `index:sourcetype` | `<_bkt>::<_cd>`, the bucket
    and offset that identify one event | `search index=<index>
    sourcetype=<sourcetype> | where _bkt="<_bkt>" AND _cd="<_cd>"` |

    | `crowdstrike_logscale` | repository or view | the event's `@id` | `@id =
    "<id>"` in that repository |

    | `outlook` | — | Microsoft Graph message id | `GET
    /users/{user}/messages/{id}` |

    | `msft-calendar` | — | Microsoft Graph event id | `GET
    /users/{user}/events/{id}` |

    | `teams` | — | `<chatId>:<messageId>` | `GET
    /users/{user}/chats/{chatId}/messages/{messageId}` |

    | `office365` | — | unified audit log record `id` | Search the user's audit
    log in Microsoft Purview and match on `id` |
servers:
  - url: https://eris.platform.total.truu.ai/api/v1/external
    description: TOTAL Public API
security:
  - ApiKeyAuth: []
tags:
  - name: Cases
    description: Threat investigation cases and verdicts
  - name: Users
    description: Provisioned users and their cases
  - name: Personas
    description: Personas, as the TOTAL persona page shows them
  - name: Threats
    description: Raw threat events
  - name: NHI
    description: Non-human identity personas and cluster catalogue
  - name: Watchlist
    description: People under watch
paths:
  /cases/{case_id}/claim:
    post:
      tags:
        - Cases
      summary: Claim a case for investigation
      description: |
        Takes ownership of a `discover` case. It moves to
        `pending_investigation` and leaves the analysts' triage queue, exactly
        as when an analyst escalates it in TOTAL. The request body is optional.

        A case already claimed is left as it is (`claimed: false`), so a retry
        is safe. A closed case returns `409`.
      operationId: claimCase
      parameters:
        - name: case_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CaseClaimRequest'
      responses:
        '200':
          description: Case claimed, or already claimed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CaseClaimResponse'
        '400':
          description: Invalid case_id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient scope — cases:write required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Case not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Case is closed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Request body validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '423':
          description: Resource not ready yet for this domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResourceNotReady'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CaseClaimRequest:
      type: object
      properties:
        comment:
          type: string
          maxLength: 2000
          description: >-
            Why the case is being claimed, e.g. the SOAR ticket. Recorded with
            the claim.
    CaseClaimResponse:
      type: object
      required:
        - case_id
        - claimed
        - case_state
      properties:
        case_id:
          type: string
          format: uuid
        claimed:
          type: boolean
          description: >-
            True when this call claimed the case; false when it was already
            claimed.
        case_state:
          $ref: '#/components/schemas/CaseState'
    Error:
      type: object
      properties:
        error:
          type: string
        details:
          type: array
          description: >-
            422 only: where the body is invalid and why. Never echoes the values
            sent.
          items:
            type: object
            properties:
              loc:
                type: array
                items: {}
              msg:
                type: string
              type:
                type: string
        request_id:
          type: string
          description: '500 only: quote to support.'
    ResourceNotReady:
      type: object
      properties:
        error:
          type: string
          description: Always `not_available`
        resource:
          type: string
          description: >-
            One of `cases`, `threats`, `users`, `watchlist`, `personas`, `nhi`,
            `nhi-clusters`
        message:
          type: string
          description: Human-readable readiness status message
    CaseState:
      type: string
      description: >
        Lifecycle state of a case:

        - `discover` — Vetted by the AI judge, ready for triage

        - `pending_investigation` — Claimed for investigation, by an analyst in
        TOTAL or through `POST /cases/{case_id}/claim`

        - `investigating` — Investigation underway

        - `closed` — Case closed
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Domain-scoped API key. Generate and manage keys from the API Keys
        page in Settings.

        Accepted in two forms:
        - `X-API-Key: <key>` header (preferred)
        - `Authorization: Bearer <key>` header (also accepted)

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.