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:
- contracts/ui-contracts.json - JSON Schema bundle with all endpoint schemas, enum definitions, and endpoint metadata
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¶
- 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
- Consistent pagination - All list endpoints use cursor-based pagination
- Consistent filtering - Filter parameters follow a standard pattern
- Consistent errors - Error responses use a uniform structure
- 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¶
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¶
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¶
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¶
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¶
For live resolution preview (without a notification):
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¶
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¶
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¶
Returns full InvariantEntry with all details.
Invariant Summary Statistics¶
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¶
For notification-specific audit events:
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¶
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¶
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¶
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¶
Request Body¶
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.
Related Documentation¶
- UI Scope - v1 scope and constraints
- Delivery Semantics - Delivery behavior details
- Policy Lifecycle - Policy resolution rules
- Invariants - Invariant enforcement process and schema (replaces historical invariants.md)