---
title: "Attribute Observations"
method: POST
path: "/api/v1/attributes/observations"
tags: ["Attributes"]
---

# Attribute Observations

`POST /api/v1/attributes/observations`

## Request

- Base URL: `https://api.athenahq.ai`
- URL: `https://api.athenahq.ai/api/v1/attributes/observations`
- Auth: API key in header `x-api-key`

Returns individual brand trait observations: each row is one statement an AI response made about one entity (your brand or a competitor) for one attribute, with the supporting response excerpt as `text_span`. `score` runs from -1 to 1 and its sign always matches `positive`; its magnitude (0.1 to 1.0) is how strongly the wording praises or criticizes. `score` is `null` when the observation was not scored, because it was extracted before scoring existed or has no valid value. If you only need the direction, use `score ?? (positive ? 1 : -1)`. Omit `positive` for both directions. `sort: "intensity"` (the default) returns the strongest observations first with unscored ones last; `sort: "recent"` returns the newest first. A page can hold fewer than `page_size` rows when an excerpt is no longer available, so use `has_more` to decide whether to fetch the next page. Also available as the MCP tool `get_attribute_observations`.

## Request body

- AttributeObservationsRequest
  - `website_id` string, uuid, required
  - `filters` ApiFiltersNoCompetitors, required: The shared filters without competitor_ids, for the brand trait endpoints that do not take it: /api/v1/attributes/cumulative is brand-only, and /api/v1/attributes/observations selects entities with `entity_ids`. Sending competitor_ids is rejected. Use /api/v1/attributes/competitors for per-competitor metrics.
    - `schedule_id` union: Select one streaming schedule. Use the selection returned by GET /api/v1/prompt-schedules. Default includes all pre-cutover history and ad hoc runs; archived schedules retain results. The retired manual token is accepted as an alias of default. This field takes one schedule; a list returns 400.
      - 'default' | 'manual'
      - string, uuid
    - `start_date` string, date-time, required: Filter start date (UTC)
    - `end_date` string, date-time: Filter end date (UTC)
    - `models` string[]: Filter by AI models
    - `prompt_ids` string[]: Filter by specific prompt IDs
    - `location_ids` string[]: Filter by location IDs. Pass this in the JSON body as `filters.location_ids`, even when filtering by a single location. Use IDs returned by `GET /api/v1/locations`.
    - `prompt_status` 'active' | 'paused': Filter by prompt status
    - `prompt_type` 'branded' | 'non_branded': Filter by prompt type
  - `entity_ids` string[]: Entities to return observations for: the `website_id` for your brand, or competitor ids from `GET /api/v1/competitors`. Omit for every entity.
  - `attribute_ids` string[]: Attributes to return observations for, from `GET /api/v1/attributes`. Omit for every active attribute.
  - `positive` boolean: `true` for favorable observations only, `false` for critical ones only. Omit for both directions.
  - `sort` 'intensity' | 'recent': `intensity`: strongest first, unscored last, then newest. `recent`: newest first.
  - `page_num` integer: Zero-indexed page number.
  - `page_size` integer: Rows per page.

## Response `200`

Successful response with a page of observations

- object
  - `data` AttributeObservation[], required
    - `observation_id` string, uuid, required
    - `response_id` string, uuid, required: The AI response the excerpt comes from.
    - `entity_id` string, uuid, required: The entity described: your `website_id` for your brand, otherwise a competitor id.
    - `entity_name` string, required
    - `is_website` boolean, required: `true` when the entity is your brand.
    - `attribute_id` string, uuid, required
    - `attribute_name` string, required
    - `positive` boolean, required: Whether this observation praises (`true`) or criticizes (`false`) the entity on this attribute.
    - `score` number, nullable, required: Signed strength from -1 to 1. The sign always matches `positive`; the magnitude (0.1 to 1.0) is how strongly the wording praises or criticizes. `null` when the observation was not scored, because it was extracted before scoring existed or has no valid value. For direction only, use `score ?? (positive ? 1 : -1)`.
    - `text_span` string, required: The excerpt of the response that supports the observation.
    - `model` string, required: AI model that generated the response, as on /api/v1/responses.
    - `created_at` string, date-time, required: ISO 8601 timestamp in UTC, to the second.
  - `page_num` integer, required: Zero-indexed page number returned.
  - `page_size` integer, required: Maximum rows per page.
  - `has_more` boolean, required: `true` if another page is available.

## Other responses

- `400`: Bad request - Invalid parameters
- `401`: Unauthorized - Invalid or missing API key
- `403`: Forbidden - API key cannot access this website
- `404`: Not found - Website does not exist

## Changes

- **2026-11-03** `2d1d86d5e067`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/athenahq/apis/athenahq-api/changes/api/v1/attributes/observations/post.md)

---

[API](https://skmtc.dev/athenahq/apis/athenahq-api.md) - [All operations](https://skmtc.dev/athenahq/apis/athenahq-api/llms.txt) - [OpenAPI document](https://skmtc.dev/athenahq/apis/athenahq-api/revisions/6cf9ca58edf6?raw)
