Skip to content

UNS Console Data Contracts

This document defines the response shapes, filters, pagination, and error semantics for UNS Console data endpoints.

Endpoint Path Convention

UNS Console read endpoints are mounted under /ui/* (for example, /ui/deliveries and /ui/audit). Producer/admin API endpoints use /api/v1/*; do not document Console read endpoints as /api/v1/* unless the implementation adds that public proxy.

Phase Scope

Phase Status Endpoints
Phase 0/1 Implemented Delivery timelines, effective policy, invariants, audit events
v0.2.0 Implemented Policy simulation (POST /ui/policies/simulate)
Phase 2 Planned Escalations, audit statistics

Machine-Readable Contract Snapshot

A machine-readable JSON schema snapshot of these contracts is available at:

This snapshot is generated deterministically from the Pydantic models in src/uns/api/routes_ui.py via scripts/generate_ui_contracts.py. Regenerate it when contracts change.

Design Principles

  1. Read-only / Evaluation - UI read endpoints are read-only per UI Scope v1; policy simulation is an idempotent evaluation endpoint implemented in v0.2.0
  2. Consistent pagination - All list endpoints use cursor-based pagination
  3. Consistent filtering - Filter parameters follow a standard pattern
  4. Consistent errors - Error responses use a uniform structure
  5. Type-safe - All shapes are defined with explicit types for client code generation

Common Types

Pagination

All list endpoints use cursor-based pagination for consistent, efficient traversal.

interface PaginationParams {
  cursor?: string;        // Opaque cursor from previous response
  limit?: number;         // Items per page (default: 25, max: 100)
}

interface PaginatedResponse<T> {
  items: T[];
  pagination: {
    next_cursor: string | null;  // null when no more pages
    has_more: boolean;
    total_count?: number;        // Optional; included when efficient to compute
  };
}

Time Range Filters

Common time-based filtering for audit and event queries.

interface TimeRangeFilter {
  start_time?: string;    // ISO 8601 timestamp (inclusive)
  end_time?: string;      // ISO 8601 timestamp (exclusive)
}

Error Response

All errors follow a consistent structure.

interface ErrorResponse {
  error: {
    code: string;         // Machine-readable error code
    message: string;      // Human-readable description
    details?: Record<string, unknown>;  // Additional context
  };
  request_id: string;     // For correlation/support
}

Standard Error Codes

Code HTTP Status Description
not_found 404 Resource does not exist
invalid_parameter 400 Malformed or invalid parameter value
invalid_cursor 400 Pagination cursor is invalid or expired
rate_limited 429 Too many requests
internal_error 500 Unexpected server error

1. Delivery Timeline + Attempts

View notification delivery history including all channel attempts, outcomes, and timing.

Endpoint

GET /ui/deliveries/{notification_id}

Response Shape

interface DeliveryTimelineResponse {
  notification_id: string;          // UUID
  notification_type: string;
  intent: "SAFETY" | "GOVERNANCE" | "OPERATIONAL" | "INFORMATIONAL";
  delivery_mode: "GUARANTEED" | "BEST_EFFORT";

  // Summary
  status: "PENDING" | "DELIVERED" | "SUCCESS" | "FAILED" | "DEDUPLICATED" | "RATE_LIMITED";
  created_at: string;               // ISO 8601
  delivered_at: string | null;      // ISO 8601, null if not delivered

  // Channel attempts in chronological order
  attempts: DeliveryAttempt[];

  // Aggregates
  channels_attempted: Channel[];
  channels_succeeded: Channel[];
  channels_failed: Channel[];

  // Context
  autonomy_context: {
    autonomy_mode: "PREVIEW" | "SHADOW" | "LIVE";
    agent_id: string;               // UUID
    plan_id?: string;               // UUID, optional
    decision_snapshot_id?: string;  // UUID, optional
    execution_snapshot_id?: string; // UUID, optional
    skip_snapshot_id?: string;      // UUID, optional
  };
}

interface DeliveryAttempt {
  attempt_id: string;               // UUID
  channel: Channel;
  status: "SUCCESS" | "FAILED" | "RATE_LIMITED" | "SKIPPED";
  started_at: string;               // ISO 8601
  completed_at: string;             // ISO 8601
  duration_ms: number;
  error_message?: string;           // Present if failed
  error_code?: string;              // Adapter-specific error code
  metadata?: {
    message_id?: string;            // External ID (e.g., Slack ts)
    retry_count?: number;
    [key: string]: unknown;
  };
}

type Channel = "SLACK" | "EMAIL" | "WEBSOCKET" | "PAGERDUTY";

List Delivery Timelines

GET /ui/deliveries

Query multiple delivery timelines with filtering.

Query Parameters

Parameter Type Description
cursor string Pagination cursor
limit number Items per page (default: 25, max: 100)
start_time string ISO 8601, filter by created_at >=
end_time string ISO 8601, filter by created_at <
status string Filter by delivery status
intent string Filter by notification intent
delivery_mode string Filter by GUARANTEED or BEST_EFFORT
agent_id string Filter by agent UUID
autonomy_mode string Filter by PREVIEW, SHADOW, or LIVE
notification_type string Filter by notification_type (prefix match supported with *)
channel string Filter by channel attempted

Response

interface DeliveryTimelineListResponse extends PaginatedResponse<DeliveryTimelineSummary> {}

interface DeliveryTimelineSummary {
  notification_id: string;
  notification_type: string;
  intent: string;
  delivery_mode: string;
  status: string;
  created_at: string;
  delivered_at: string | null;
  channels_attempted: Channel[];
  channels_succeeded: Channel[];
  autonomy_mode: string;
  agent_id: string;
}

Errors

Code Condition
not_found notification_id does not exist
invalid_parameter Invalid filter value

2. Escalations + Failures

View escalation events and delivery failures, particularly for GUARANTEED delivery mode.

Endpoint

Planned: GET /ui/escalations

Query Parameters

Parameter Type Description
cursor string Pagination cursor
limit number Items per page (default: 25, max: 100)
start_time string ISO 8601, filter by escalated_at >=
end_time string ISO 8601, filter by escalated_at <
intent string Filter by notification intent
agent_id string Filter by agent UUID
autonomy_mode string Filter by autonomy mode
notification_type string Filter by notification_type (prefix match supported)
escalation_type string Filter by delivery_failure or ack_timeout

Response Shape

interface EscalationListResponse extends PaginatedResponse<EscalationEvent> {}

interface EscalationEvent {
  escalation_id: string;            // UUID
  escalation_type: "delivery_failure" | "ack_timeout";
  escalated_at: string;             // ISO 8601

  // Notification context
  notification_id: string;
  notification_type: string;
  intent: "SAFETY" | "GOVERNANCE" | "OPERATIONAL" | "INFORMATIONAL";
  title: string;

  // Autonomy context
  autonomy_context: {
    autonomy_mode: "PREVIEW" | "SHADOW" | "LIVE";
    agent_id: string;
    plan_id?: string;
  };

  // Escalation details (varies by type)
  details: DeliveryFailureDetails | AckTimeoutDetails;
}

interface DeliveryFailureDetails {
  type: "delivery_failure";
  delivery_mode: "GUARANTEED";      // Only GUARANTEED triggers escalation
  channel_errors: ChannelError[];
  total_attempts: number;
  first_attempt_at: string;
  last_attempt_at: string;
}

interface ChannelError {
  channel: Channel;
  error_message: string;
  error_code?: string;
  attempted_at: string;
}

interface AckTimeoutDetails {
  type: "ack_timeout";
  timeout_minutes: number;
  created_at: string;               // Notification creation time
  timeout_at: string;               // When timeout occurred
  ack_status: "TIMED_OUT";
}

Get Single Escalation

Planned: GET /ui/escalations/{escalation_id}

Returns full EscalationEvent with complete details.

Errors

Code Condition
not_found escalation_id does not exist
invalid_parameter Invalid filter value

3. Effective Policy Resolution Trace

View how a notification's routing was determined, showing the resolution chain from explicit override to SDK default.

Endpoint

GET /ui/policies/effective

For live resolution preview (without a notification):

GET /ui/policies/effective

Query Parameters (Required)

Parameter Type Description
notification_type string The notification type to resolve
intent string SAFETY, GOVERNANCE, OPERATIONAL, or INFORMATIONAL
autonomy_mode string PREVIEW, SHADOW, or LIVE
irreversible bool Whether the action is irreversible (default: false)

Response Shape

interface PolicyResolutionTraceResponse {
  notification_type: string;

  // Effective values (what would be used)
  effective: {
    delivery_mode: "GUARANTEED" | "BEST_EFFORT";
    channels: Channel[];
    require_ack: boolean;
    ack_timeout_minutes: number | null;
    dedup_window_seconds: number;
  };

  // Resolution trace for each field
  resolution: {
    delivery_mode: ResolutionStep[];
    channels: ResolutionStep[];
    require_ack: ResolutionStep[];
    ack_timeout_minutes: ResolutionStep[];
    dedup_window_seconds: ResolutionStep[];
  };

  // Context used for resolution
  resolution_context: {
    intent: "SAFETY" | "GOVERNANCE" | "OPERATIONAL" | "INFORMATIONAL";
    autonomy_mode: "PREVIEW" | "SHADOW" | "LIVE";
    irreversible: boolean;
    policy_found: boolean;
    policy_notification_type?: string;  // Present if policy was found
  };
}

interface ResolutionStep {
  source: "explicit_override" | "mode_override" | "policy_base" | "sdk_default";
  value: unknown;                   // The value at this resolution step
  applied: boolean;                 // Whether this value was used
  reason?: string;                  // Explanation if not applied
}

Resolution Order Documentation

The resolution field shows the full resolution chain per Policy Lifecycle:

1. explicit_override  - Values passed directly to SDK.send()
2. mode_override      - Policy mode-specific override (preview_override, etc.)
3. policy_base        - Policy base value
4. sdk_default        - SDK default derived from intent + autonomy_mode

Errors

Code Condition
invalid_parameter Missing required parameters or invalid enum value

Policy Simulation

Planned: POST /ui/policies/simulate

Preview how a hypothetical notification would be resolved without sending it.

Request Body

interface PolicySimulationRequest {
  notification_type: string;
  intent: "SAFETY" | "GOVERNANCE" | "OPERATIONAL" | "INFORMATIONAL";
  autonomy_mode: "PREVIEW" | "SHADOW" | "LIVE";
  irreversible?: boolean;           // Default: false

  // Optional explicit overrides to test
  overrides?: {
    delivery_mode?: "GUARANTEED" | "BEST_EFFORT";
    channels?: Channel[];
    require_ack?: boolean;
  };
}

Response

Returns PolicyResolutionTraceResponse showing how the notification would be resolved.


4. Invariant Registry Rendering

View the system invariant registry for compliance and audit purposes.

Endpoint

GET /ui/invariants

Query Parameters

Parameter Type Description
cursor string Pagination cursor
limit number Items per page (default: 25, max: 100)
severity string Filter by HIGH, MEDIUM, or LOW
enforcement_type string Filter by guard, isolation, test, or exception
status string Filter by active or inactive
search string Full-text search on statement

Response Shape

interface InvariantListResponse extends PaginatedResponse<InvariantEntry> {}

interface InvariantEntry {
  id: string;                       // e.g., "INV-ACK-001"
  statement: string;                // The invariant claim
  severity: "HIGH" | "MEDIUM" | "LOW";
  status: "active" | "inactive";

  enforcement: {
    type: "guard" | "isolation" | "test" | "exception";
    description: string;
  };

  evidence: EvidenceLink[];

  owner: string;                    // Team/role responsible
  last_reviewed: string;            // ISO 8601 date

  // For exceptions only
  mitigation?: string[];
}

interface EvidenceLink {
  path: string;                     // Repo-relative path
  note: string;                     // Description of what this evidences
  line_ref?: string;                // Optional line reference
}

Get Single Invariant

Planned: GET /ui/invariants/{invariant_id}

Returns full InvariantEntry with all details.

Invariant Summary Statistics

GET /ui/invariants/summary
interface InvariantSummaryResponse {
  total_count: number;
  by_severity: {
    HIGH: number;
    MEDIUM: number;
    LOW: number;
  };
  by_enforcement_type: {
    guard: number;
    isolation: number;
    test: number;
    exception: number;
  };
  by_status: {
    active: number;
    inactive: number;
  };
  exceptions_requiring_review: number;  // Exceptions past review date
  last_updated: string;                 // ISO 8601
}

Errors

Code Condition
not_found invariant_id does not exist
invalid_parameter Invalid filter value

5. Audit Event Stream

View the audit trail of all notification-related events.

Endpoint

GET /ui/audit

For notification-specific audit events:

GET /ui/audit?notification_id={notification_id}

Query Parameters

Parameter Type Description
cursor string Pagination cursor
limit number Items per page (default: 25, max: 100)
start_time string ISO 8601, filter by event_time >=
end_time string ISO 8601, filter by event_time <
event_type string Filter by event type (see below)
notification_id string Filter by notification UUID
agent_id string Filter by agent UUID
autonomy_mode string Filter by PREVIEW, SHADOW, or LIVE
correlation_id string Filter by correlation ID

Event Types

Type Description
notification.created Notification submitted to UNS
delivery.started Delivery attempt initiated
delivery.channel_attempted Individual channel attempt
delivery.succeeded Delivery completed successfully
delivery.failed Delivery failed (all channels)
delivery.deduplicated Notification deduplicated
delivery.rate_limited Channel rate limited
ack.pending Acknowledgment tracking started
ack.received Notification acknowledged
ack.dismissed Notification dismissed
ack.timed_out Acknowledgment timeout
escalation.triggered Escalation callback invoked

Response Shape

interface AuditEventListResponse extends PaginatedResponse<AuditEvent> {}

interface AuditEvent {
  audit_id: string;                 // UUID
  event_type: string;               // One of the event types above
  event_time: string;               // ISO 8601

  // Notification context (always present)
  notification_id: string;
  notification_type: string;

  // Autonomy context
  autonomy_context: {
    autonomy_mode: "PREVIEW" | "SHADOW" | "LIVE";
    agent_id: string;
    plan_id?: string;
    decision_snapshot_id?: string;
    execution_snapshot_id?: string;
    skip_snapshot_id?: string;
  };

  // Event-specific payload
  payload: AuditEventPayload;

  // Correlation
  correlation_id?: string;
}

// Type union for event-specific payloads
type AuditEventPayload =
  | NotificationCreatedPayload
  | DeliveryOutcomePayload
  | ChannelAttemptPayload
  | AckStatusChangePayload
  | EscalationPayload;

interface NotificationCreatedPayload {
  type: "notification.created";
  notification_type: string;
  intent: string;
  delivery_mode: string;
  channels: Channel[];
  require_ack: boolean;
  title: string;
  source: string;
}

interface DeliveryOutcomePayload {
  type: "delivery.started" | "delivery.succeeded" | "delivery.failed" | "delivery.deduplicated" | "delivery.rate_limited";
  status: string;
  channels_attempted: Channel[];
  channels_succeeded: Channel[];
  channels_failed: Channel[];
  error_message?: string;
  error_code?: string;
  duration_ms?: number;
}

interface ChannelAttemptPayload {
  type: "delivery.channel_attempted";
  channel: Channel;
  status: "SUCCESS" | "FAILED" | "RATE_LIMITED" | "SKIPPED";
  error_message?: string;
  error_code?: string;
  duration_ms: number;
  metadata?: Record<string, unknown>;
}

interface AckStatusChangePayload {
  type: "ack.pending" | "ack.received" | "ack.dismissed" | "ack.timed_out";
  old_status: string | null;
  new_status: string;
  acked_by?: string;
  acked_at?: string;
  timeout_minutes?: number;
}

interface EscalationPayload {
  type: "escalation.triggered";
  escalation_type: "delivery_failure" | "ack_timeout";
  escalation_id: string;
  callback_invoked: boolean;
  callback_result?: string;
}

Errors

Code Condition
not_found notification_id does not exist
invalid_parameter Invalid filter value or time range
invalid_cursor Cursor expired or malformed

Audit Event Statistics

Planned: GET /ui/audit/statistics

Query Parameters

Parameter Type Description
start_time string ISO 8601 (required)
end_time string ISO 8601 (required)
granularity string minute, hour, day (default: hour)
interface AuditStatisticsResponse {
  time_range: {
    start_time: string;
    end_time: string;
    granularity: string;
  };

  totals: {
    total_events: number;
    by_event_type: Record<string, number>;
    by_intent: Record<string, number>;
    by_autonomy_mode: Record<string, number>;
  };

  time_series: TimeSeriesBucket[];

  delivery_metrics: {
    total_deliveries: number;
    successful: number;
    failed: number;
    deduplicated: number;
    success_rate: number;           // 0.0 - 1.0
  };

  ack_metrics: {
    total_acks_required: number;
    acknowledged: number;
    dismissed: number;
    timed_out: number;
    ack_rate: number;               // 0.0 - 1.0
  };
}

interface TimeSeriesBucket {
  timestamp: string;                // ISO 8601, bucket start time
  event_count: number;
  by_event_type: Record<string, number>;
}

6. Console Session & Policy Lifecycle Read Models

The UNS Console also consumes five read-only /ui/* endpoints for principal session capability discovery (GET /ui/session) and policy lifecycle / shadow-evaluation inspection (GET /ui/policies/active, GET /ui/policies/revisions, GET /ui/policies/proposals, and GET /ui/policies/proposals/{delta_id}).

Console Session Capabilities

GET /ui/session
interface ConsoleSessionResponse {
  subject: string;
  scopes: string[];
  persistence_mode?: string | null;
}

Active Policy Catalog & Policy Revisions

GET /ui/policies/active
GET /ui/policies/revisions?notification_type={type}&status={ACTIVE|DRAFT|PREVIEW|RETIRED}&limit={limit}
interface ActivePolicySummary {
  notification_type: string;
  version: string;
  status: string;
  intent?: NotificationIntent | null;
  channels: Channel[];
  delivery_mode: DeliveryMode;
  require_ack: boolean;
  ack_timeout_minutes?: number | null;
  ack_timeout_seconds?: number | null;
  dedup_window_seconds: number;
}

interface ActivePolicyListResponse {
  items: ActivePolicySummary[];
  total_count: number;
}

interface PolicyRevisionSummary extends ActivePolicySummary {
  policy_id: string;
  config_hash: string;
  previous_version_id?: string | null;
  created_at?: string | null;
  updated_at?: string | null;
  activated_at?: string | null;
  retired_at?: string | null;
}

interface PolicyRevisionListResponse {
  items: PolicyRevisionSummary[];
  total_count: number;
}

Policy Delta Proposals & Shadow Evaluation Evidence

GET /ui/policies/proposals?policy_type={type}&state={PROPOSED|SHADOW|APPROVED|REJECTED|APPLIED}&limit={limit}
GET /ui/policies/proposals/{delta_id}
interface PolicyProposalSummary {
  delta_id: string;
  policy_type: string;
  field_path: string;
  current_value: unknown;
  proposed_value: unknown;
  reasoning: string;
  confidence: number;
  evidence_count: number;
  state: "PROPOSED" | "SHADOW" | "APPROVED" | "REJECTED" | "APPLIED";
  baseline_hash?: string | null;
  baseline_version?: number | null;
  candidate_hash: string;
  approval_candidate_hash?: string | null;
  current_active_hash?: string | null;
  current_active_version?: number | null;
  baseline_hash_matches: boolean;
  evidence_hash_matches: boolean;
  can_promote_to_shadow: boolean;
  can_approve: boolean;
  can_activate: boolean;
  draft_policy_id?: string | null;
  draft_version?: string | null;
  required_guards: string[];
  guard_results: PolicyGuardResultItem[];
  transition_history: PolicyTransitionItem[];
  shadow_aggregate?: ShadowEvaluationSummaryItem | null;
  shadow_results: ShadowEvaluationSampleItem[];
  proposed_by: string;
  approved_by?: string | null;
  rejected_by?: string | null;
  rejection_reason?: string | null;
  applied_at?: string | null;
  created_at: string;
  updated_at: string;
}

interface PolicyProposalListResponse {
  items: PolicyProposalSummary[];
  total_count: number;
}

Future Endpoints (Planned)

The following endpoints are designed but not yet fully implemented. They are documented here for planning purposes.

F1. Real-time Event Streaming

Status: Planned for Phase 3

WebSocket endpoint for real-time audit event streaming.

Endpoint

Planned: WS /ui/audit/stream

Connection Parameters

Parameter Type Description
event_types string[] Filter by event types (optional)
agent_id string Filter by agent UUID (optional)
autonomy_mode string Filter by autonomy mode (optional)

Message Format

interface StreamMessage {
  type: "event" | "heartbeat" | "error";
  timestamp: string;
  payload: AuditEvent | HeartbeatPayload | ErrorPayload;
}

F2. Batch Operations

Status: Planned for Phase 3

Batch queries for multiple notifications.

Endpoint

Planned: POST /ui/deliveries/batch

Request Body

interface BatchTimelineRequest {
  notification_ids: string[];       // Max 100
}

Response

interface BatchTimelineResponse {
  results: Record<string, DeliveryTimelineResponse | null>;
  errors: Record<string, ErrorDetail>;
}

Reference

HTTP Status Code Summary

Status Meaning
200 Success
400 Bad request (invalid parameters)
404 Resource not found
429 Rate limited
500 Internal server error

Rate Limiting

UNS Console endpoints do not currently add HTTP rate-limit headers. Channel delivery rate limiting is documented separately in Delivery Semantics and API Reference.

Cursor Semantics

  • Cursors are opaque strings generated by the read model adapter; clients must not parse or construct them.
  • Invalid cursors currently restart pagination from the first page and are logged.
  • Cursors should be treated as specific to the original query parameters; changing filters should start a new query.