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

# Report agent activity

> **Prototype (AG-285).** Reports activity observed by an agent runtime on a developer's machine - today, Claude Code hook events.

This is **telemetry from cooperating machines, not attestation**. Opal can only see what a configured, consenting client chooses to send, cannot verify that a fleet is configured at all, and has no way to detect a machine that is reporting nothing. Absence of activity for an agent therefore means "nothing was reported", never "the agent did nothing".

The request body is either a batch envelope (`{"events": [...]}`), a bare JSON array of events, or a single raw Claude Code hook payload - the last so a hook can POST here directly with no relay in between.

The bearer token establishes the **organization** and nothing finer. No field in the payload is treated as an identity claim Opal trusts: the actor headers below are used for best-effort attribution and display only. Events that cannot be attributed to an agent are retained and surfaced as unattributed activity rather than dropped.



## OpenAPI

````yaml https://app.opal.dev/openapi.yaml post /agent-activity
openapi: 3.1.0
info:
  contact:
    email: hello@opal.dev
    name: Opal Team
    url: https://www.opal.dev/
  description: >-
    The Opal API is a RESTful API that allows you to interact with the Opal
    Security platform programmatically.
  title: Opal API
  version: '1.0'
servers:
  - description: Production
    url: https://api.opal.dev/v1
security: []
tags:
  - name: access-rules
    description: Operations related to access rules
  - name: agent-activity
    description: Operations related to agent activity telemetry
  - name: apps
    description: Operations related to apps
  - name: bundles
    description: Operations related to bundles
  - name: campaigns
    description: Operations related to access review campaigns
  - name: configuration-templates
    description: Operations related to configuration templates
  - name: delegations
    description: Operations related to request reviewer delegations
  - name: event-streams
    description: Operations related to event streaming connections
  - name: events
    description: Operations related to events
  - name: groups
    description: Operations related to groups
  - name: group-bindings
    description: Operations related to group bindings
  - name: idp-group-mappings
    description: Operations related to IDP group mappings
  - name: message-channels
    description: Operations related to message channels
  - name: non-human-identities
    description: Operations related to non-human identities
  - name: on-call-schedules
    description: Operations related to on-call schedules
  - name: opal-queries
    description: Operations related to OpalQuery
  - name: opal-scripts
    description: Operations related to OpalScripts
  - name: owners
    description: Operations related to owners
  - name: requests
    description: Operations related to requests
  - name: resources
    description: Operations related to resources
  - name: role-assignments
    description: >-
      Operations related to role assignments (general grants between an entity
      and a principal)
  - name: paladin
    description: Operations related to Paladin
  - name: sessions
    description: Operations related to sessions
  - name: tags
    description: Operations related to tags
  - name: tokens
    description: Operations related to API tokens
  - name: uars
    description: Operations related to UARs. Deprecated in favor of the `campaigns` API.
  - name: users
    description: Operations related to users
paths:
  /agent-activity:
    post:
      tags:
        - agent-activity
      summary: Report agent activity
      description: >-
        **Prototype (AG-285).** Reports activity observed by an agent runtime on
        a developer's machine - today, Claude Code hook events.


        This is **telemetry from cooperating machines, not attestation**. Opal
        can only see what a configured, consenting client chooses to send,
        cannot verify that a fleet is configured at all, and has no way to
        detect a machine that is reporting nothing. Absence of activity for an
        agent therefore means "nothing was reported", never "the agent did
        nothing".


        The request body is either a batch envelope (`{"events": [...]}`), a
        bare JSON array of events, or a single raw Claude Code hook payload -
        the last so a hook can POST here directly with no relay in between.


        The bearer token establishes the **organization** and nothing finer. No
        field in the payload is treated as an identity claim Opal trusts: the
        actor headers below are used for best-effort attribution and display
        only. Events that cannot be attributed to an agent are retained and
        surfaced as unattributed activity rather than dropped.
      operationId: reportAgentActivity
      parameters:
        - description: >-
            The identity the reporting client claims this activity belongs to -
            an email or an Anthropic user id. **Unverified**: a Claude Code hook
            payload contains no user identity, so this has to be injected by
            whoever deploys the hook (via the http handler's `headers` map and
            `allowedEnvVars` interpolation), and whoever controls the machine
            controls the header. Used only when the API key itself does not
            identify a developer. Omitting it is valid.
          explode: false
          in: header
          name: X-Opal-Actor
          required: false
          schema:
            type: string
          style: simple
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentActivityIngestRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentActivityIngestResult'
          description: A per-batch accounting of what was accepted, stored, and attributed.
        '400':
          description: >-
            The body was unparseable, empty, or carried more events than one
            request may.
        '403':
          description: Agent activity ingest is not enabled for this organization.
        '413':
          description: The request body exceeded the maximum accepted size.
      security:
        - BearerAuth: []
components:
  schemas:
    AgentActivityIngestRequest:
      description: >-
        # AgentActivityIngestRequest Object

        ### Description

        A batch of agent activity events.


        Three body shapes are accepted: this envelope (`{"events": [...]}`), a
        bare JSON

        array of `AgentActivityEvent`, and a single `AgentActivityEvent` object.
        The last

        exists so a Claude Code hook can POST here directly, since a hook sends
        one bare

        payload per event and cannot wrap it.
      properties:
        events:
          items:
            $ref: '#/components/schemas/AgentActivityEvent'
          type: array
      type: object
      additionalProperties: true
    AgentActivityIngestResult:
      description: >-
        # AgentActivityIngestResult Object

        ### Description

        What one ingest request actually did.


        `received` and `stored` differ when a delivery was retried and
        deduplicated.

        `attributed` and `unattributed` sum to `received` rather than to
        `stored`, because

        attribution happens before deduplication. Unattributed events are kept,
        not dropped:

        activity Opal cannot tie to a governed agent is itself a governance
        finding.
      properties:
        received:
          description: Events parsed out of the request body.
          type: integer
        stored:
          description: >-
            Rows actually written. Lower than `received` when a retried delivery
            was deduplicated on `tool_use_id`.
          type: integer
        attributed:
          description: Events that resolved to an agent in this organization.
          type: integer
        unattributed:
          description: >-
            Events that resolved to no agent, and are therefore visible only in
            the unattributed activity list. A persistently high value means the
            reporting clients are not sending an identity Opal can resolve.
          type: integer
      required:
        - received
        - stored
        - attributed
        - unattributed
      type: object
    AgentActivityEvent:
      description: >-
        # AgentActivityEvent Object

        ### Description

        One activity event reported by an agent runtime. Today this is a Claude
        Code hook

        payload, posted either by the hook itself or by a relay that batches
        them.


        Only the fields below are read; everything else in the payload is parsed
        and

        **discarded**, never persisted. That still includes `tool_response` (the
        result

        body, which carries file contents and third-party workspace data) and

        `transcript_path` / `scratchpad_dir` (absolute paths on an employee's
        own laptop,

        which Opal cannot govern and should not be accumulating).


        `tool_input` IS now read and stored. It was previously discarded pending
        a

        data-handling decision; that decision has since been made, and without
        arguments

        no policy can name what a call acted on - a rule about a file path, a
        command

        family or a web domain refers to something that exists only inside
        `tool_input`.
      properties:
        hook_event_name:
          description: >-
            Which hook fired, e.g. `PostToolUse`, `PreToolUse`,
            `PermissionDenied`. Required; an event with no name cannot be
            interpreted and is rejected.
          type: string
        tool_input:
          description: >-
            The call's arguments, as the runtime sent them. Server-defined for
            an MCP tool;

            a fixed shape per built-in (`command` for Bash, `file_path` for
            Edit, `url` for

            WebFetch).


            Stored as sent, and separately reduced to a normalized target the
            activity feed

            and policy can match on. The stored copy is the record of fact; the
            derived

            target is recomputed whenever the extractor improves.


            Capped before storage. A capped payload is no longer valid JSON, so
            the row

            records that it was capped rather than leaving each reader to
            discover it by

            failing to parse.
          type: object
          additionalProperties: true
        session_id:
          description: The agent session this event belongs to. Opaque to Opal.
          type: string
        prompt_id:
          description: >-
            Groups every tool call made under one user instruction, so a run can
            be read back as the instruction that caused it rather than as
            unrelated rows.
          type: string
        agent_id:
          description: >-
            Set only when the call came from an autonomous subagent rather than
            the session a person is typing into. Stored as `subagent_id` -
            Opal's own `agent_id` already means something else.
          type: string
        agent_type:
          description: >-
            The subagent's type, e.g. `general-purpose`. Set alongside
            `agent_id`.
          type: string
        permission_mode:
          description: >-
            The session's permission mode, e.g. `default`, `acceptEdits`,
            `bypassPermissions`. A session running `bypassPermissions` had no
            approval gate in front of any tool call it made.
          type: string
        actor_email:
          description: >-
            The identity the sender reports for this specific event.
            **Unverified**, the same as `X-Opal-Actor`, and used only when
            neither the API key nor that header identified a developer. An
            `http` hook cannot populate this; a `command`-type hook or a relay
            can (`$USER`, a hostname, `git config user.email`), which is why it
            is per-event rather than per-request - a relay can batch several
            developers.
          type: string
        transcript_path:
          description: >-
            Read at ingest **only** to parse an operating-system username out
            of, as the last-resort attribution step, and then discarded. The
            path itself is never stored: it is an absolute path on an employee's
            laptop and Opal has no business accumulating those.
          type: string
        tool_name:
          description: >-
            The tool invoked, for tool-scoped events. Empty for session-level
            events.
          type: string
        tool_use_id:
          description: >-
            The tool invocation's id, used together with `hook_event_name` to
            deduplicate a retried delivery. Absent on events that carry none,
            which are stored at-least-once.
          type: string
        cwd:
          description: The working directory the agent was operating in.
          type: string
        timestamp:
          description: >-
            When the client says the event occurred (RFC 3339). Clamped
            server-side to a window around receipt, because a client-controlled
            sort key is otherwise a way to pin rows to the top of every list.
            Defaults to receipt time when absent or unparseable.
          format: date-time
          type: string
      required:
        - hook_event_name
      type: object
      additionalProperties: true
  securitySchemes:
    BearerAuth:
      scheme: bearer
      type: http

````

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