---
title: "Ingest a batch of agent signals"
method: POST
path: "/v1/agent-signals"
tags: ["AgentSignals"]
---

# Ingest a batch of agent signals

`POST /v1/agent-signals`

Accepts a batch of agent signals, either mined by the Tessl CLI from local agent logs or emitted at the moment they happen. The owning org and user are derived from the authenticated actor — client-supplied attribution fields are stripped. This is a best-effort validated container: the shared spine of every signal is validated, but `signalType` is free text and `attributes` is open, so a new signal type can be emitted without a coordinated backend release. `attributes` is still bounded in size and nesting depth and may never carry a tool-response body, whatever the signal type. Rejected with 400 when the org has not enabled agent-signal collection. Insertion is idempotent on `(orgId, signalType, provider, sessionId, signalTimestamp, invocation)`: a resend within the same org is a no-op, reported as a duplicate. Two distinct signal types sharing the rest of the tuple are stored independently. Timestamps are normalized to UTC milliseconds before storage, so idempotency is preserved regardless of source precision or timezone representation. Returns how many signals were newly accepted and how many were duplicates. At most 1000 signals per request. A request from a CLI older than this generic spine — sending only the deprecated `skillName`/`skillScope`/`skillTile`/`installedSkills` fields and none of `signalType`/`name`/`namespace` — is still accepted and mapped onto the fields above; rows built this way carry `attributes.legacyPayload: true`. A payload that mixes any deprecated field with any generic field (`signalType`, `name`, `namespace`), or that carries neither `signalType` nor any deprecated field, is rejected with 400: the deprecated and generic shapes cannot be combined unambiguously, so a sender must use one or the other.

## Headers

- `Authorization` string

## Request body

- object
  - `signals` object[], required
    - `signalType` string — The kind of signal, e.g. `skill.activation`, `mcp.tool_activation`, `session.available`. Free text: an unknown type is accepted and stored. Optional: a request from a CLI older than this generic spine sends no `signalType` and defaults to `skill.activation`. Known attribute keys are typed the same way for every type, and the data bounds on `attributes` (size, depth, no tool-response body) apply to every type.
    - `provider` string, required — The agent harness the signal came from, e.g. `claude-code`, `cursor-ide`, `codex`, `tessl-agent`.
    - `sessionId` string, required
    - `signalTimestamp` string, date-time, required — When the signal happened, from the log event. Any RFC 3339 date-time is accepted; the handler normalizes to UTC milliseconds before storage so idempotency is not affected by source precision or timezone representation.
    - `invocation` string, required — The raw, unparsed invocation string from the log.
    - `analyzedAt` string, date-time — When the CLI mined the signal. Any RFC 3339 date-time. Omitted for a signal reported at the moment it happens, which was never mined.
    - `namespace` string — The namespace the signal belongs to: the tile for a skill, the server for an MCP tool.
    - `name` string — The name of the thing the signal is about: the skill for a skill activation, the action for an MCP tool, the session identifier or a stable label for an availability record. Optional: a request from a CLI older than this generic spine sends no `name`, and falls back to the deprecated `skillName`, then to `invocation`.
    - `sessionGitRepo` string — `host/owner/repo` form of the session's git repo.
    - `skillName` string — Deprecated: replaced by `name`.
    - `skillScope` string — Deprecated: moves into `attributes.scope`.
    - `skillTile` string — Deprecated: replaced by `namespace`.
    - `installedSkills` object[] — Deprecated: moves into `attributes.installedSkills`.
      - `name` string, required
      - `scope` string, required
      - `tile` string
    - `attributes` object — Type-specific detail for the signal. Known keys are typed; any other key is accepted and stored as-is, subject to the data bounds the server enforces: at most 65536 UTF-8 bytes serialized, at most 8 levels of nesting, and no tool-output key (`tool_response`, `tool_output`, `tool_result`, or `toolResponse`) at any depth (tool response bodies are never stored). Omitted defaults to `{}`, or to a mapping derived from the deprecated fields below when the request carries any of them.
      - `durationMs` number
      - `model` string
      - `turnId` string
      - `toolUseId` string
      - `toolName` string
      - `resolved` boolean

## Response `200`

Default Response

- object
  - `links` object, required
    - `self` string, uri, required
  - `data` object, required
    - `id` string, required
    - `type` 'agent-signal-batch', required
    - `attributes` object, required
      - `accepted` number, required — Signals newly stored by this request.
      - `duplicates` number, required — Signals skipped as resends of an already-stored activation.

## Other responses

- `400` — Bad Request
- `401` — Unauthorized
- `403` — Forbidden
- `404` — Not Found
- `500` — Internal Server Error

## Changes

- **2026-09-15** `d50a02b79ad3` — 2 info
  - added the optional property `error/code` to the response with the `403` status
  - added the optional property `error/url` to the response with the `403` status
- **2026-09-09** `1de33e42f781` — 6 info
  - added the new optional request property `signals/items/attributes`
  - added the new optional request property `signals/items/name`
  - added the new optional request property `signals/items/namespace`
  - added the new optional request property `signals/items/signalType`
  - …2 more
- **2026-09-05** `32fc8bf52cf2` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/tessl/apis/tessl-api/changes/v1/agent-signals/post.md)

---

[API](https://skmtc.dev/tessl/apis/tessl-api.md) · [All operations](https://skmtc.dev/tessl/apis/tessl-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tessl/apis/tessl-api/revisions/288d28ba107c?raw)
