Universal Review Taxonomy v5.1 implementation with: - Track A (Training): A1 Quickstart, A2 QA Protocol, A3 Calibration Set, A4 Full Manual - Track B (Engineering): B1 Code Registry, B2 Database Schema, B3 Owner Routing, B4 API Contract - Track C (Analytics): C1 Issue Lifecycle, C2 KPI Mapping Guide - Track D (Integration): D1 Dashboard Specification Covers 7 domains, 28 categories, 138 subcodes, 16 causal codes, and 7 metadata dimensions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2896 lines
81 KiB
YAML
2896 lines
81 KiB
YAML
# =============================================================================
|
|
# B4: URT API Contract
|
|
# Universal Review Taxonomy v5.1
|
|
# =============================================================================
|
|
#
|
|
# Complete OpenAPI 3.0.3 specification for the URT classification and
|
|
# issue management API.
|
|
#
|
|
# Status: Production Ready
|
|
# Version: 1.0.0
|
|
# Date: 2026-01-23
|
|
# Depends On: B1-urt-codes.yaml, B2-database-schema.sql, B3-owner-routing.yaml
|
|
# =============================================================================
|
|
|
|
openapi: 3.0.3
|
|
info:
|
|
title: Universal Review Taxonomy API
|
|
description: |
|
|
API for managing customer review classification using the Universal Review Taxonomy (URT) v5.1.
|
|
|
|
## Overview
|
|
|
|
This API enables:
|
|
- **Review Management**: CRUD operations for customer reviews
|
|
- **Span Classification**: Classification of review text segments using URT codes
|
|
- **Issue Lifecycle**: Full issue tracking per the C1 framework
|
|
- **Code Registry**: Lookup and validation of URT taxonomy codes
|
|
- **Analytics**: Domain/category rollups and time-series metrics
|
|
- **Owner Routing**: Resolution of responsible teams per B3 routing matrix
|
|
|
|
## Implementation Profiles
|
|
|
|
The API supports four implementation profiles with varying complexity:
|
|
- **URT-Lite**: 7 domains, minimal metadata
|
|
- **URT-Core**: 28 categories, basic metadata
|
|
- **URT-Standard**: 140 subcodes, full metadata
|
|
- **URT-Full**: 156 codes including causal analysis
|
|
|
|
## Rate Limiting
|
|
|
|
All endpoints are rate limited. Check response headers for current limits:
|
|
- `X-RateLimit-Limit`: Requests allowed per window
|
|
- `X-RateLimit-Remaining`: Requests remaining in current window
|
|
- `X-RateLimit-Reset`: Unix timestamp when window resets
|
|
|
|
version: 1.0.0
|
|
contact:
|
|
name: URT API Support
|
|
email: api-support@example.com
|
|
url: https://docs.example.com/urt-api
|
|
license:
|
|
name: Proprietary
|
|
url: https://example.com/license
|
|
x-logo:
|
|
url: https://example.com/logo.png
|
|
|
|
servers:
|
|
- url: https://api.example.com/urt/v1
|
|
description: Production server
|
|
- url: https://api.staging.example.com/urt/v1
|
|
description: Staging server
|
|
- url: http://localhost:8080/urt/v1
|
|
description: Local development
|
|
|
|
tags:
|
|
- name: Reviews
|
|
description: Customer review management
|
|
- name: Spans
|
|
description: Classified text segments
|
|
- name: Issues
|
|
description: Issue lifecycle management (C1 framework)
|
|
- name: Codes
|
|
description: URT code registry and validation
|
|
- name: Analytics
|
|
description: Aggregations and metrics
|
|
- name: Routing
|
|
description: Owner routing resolution
|
|
- name: Health
|
|
description: System health and status
|
|
|
|
# =============================================================================
|
|
# SECURITY SCHEMES
|
|
# =============================================================================
|
|
|
|
security:
|
|
- BearerAuth: []
|
|
- ApiKeyAuth: []
|
|
|
|
components:
|
|
securitySchemes:
|
|
BearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: |
|
|
JWT Bearer token authentication. Obtain tokens via OAuth 2.0 flow.
|
|
|
|
Example: `Authorization: Bearer <token>`
|
|
|
|
ApiKeyAuth:
|
|
type: apiKey
|
|
in: header
|
|
name: X-API-Key
|
|
description: |
|
|
API key authentication for server-to-server communication.
|
|
|
|
Example: `X-API-Key: your-api-key`
|
|
|
|
# ===========================================================================
|
|
# SCHEMAS
|
|
# ===========================================================================
|
|
schemas:
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Core Entity Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
Review:
|
|
type: object
|
|
required:
|
|
- review_id
|
|
- source
|
|
- review_text
|
|
- created_at
|
|
properties:
|
|
review_id:
|
|
type: string
|
|
format: uuid
|
|
description: Unique review identifier
|
|
example: "550e8400-e29b-41d4-a716-446655440000"
|
|
external_id:
|
|
type: string
|
|
maxLength: 255
|
|
description: ID from source system (e.g., Google place_id)
|
|
example: "ChIJN1t_tDeuEmsRUsoyG83frY4"
|
|
source:
|
|
type: string
|
|
maxLength: 50
|
|
description: Review source platform
|
|
enum: [google, yelp, tripadvisor, facebook, trustpilot, custom]
|
|
example: "google"
|
|
business_id:
|
|
type: string
|
|
maxLength: 255
|
|
description: Business identifier
|
|
example: "BUS-2026-0001"
|
|
author_name:
|
|
type: string
|
|
maxLength: 255
|
|
description: Review author display name
|
|
example: "John D."
|
|
author_id:
|
|
type: string
|
|
maxLength: 255
|
|
description: Author identifier from source
|
|
review_text:
|
|
type: string
|
|
description: Full review text content
|
|
example: "Great food but the service was slow. Would recommend the pasta."
|
|
star_rating:
|
|
type: number
|
|
format: float
|
|
minimum: 1.0
|
|
maximum: 5.0
|
|
description: Star rating (1.0-5.0)
|
|
example: 4.0
|
|
review_date:
|
|
type: string
|
|
format: date
|
|
description: Date review was posted
|
|
example: "2026-01-15"
|
|
language_code:
|
|
type: string
|
|
maxLength: 5
|
|
description: ISO 639-1 language code
|
|
example: "en"
|
|
raw_metadata:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Source-specific metadata
|
|
span_count:
|
|
type: integer
|
|
minimum: 0
|
|
description: Number of classified spans
|
|
readOnly: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
|
|
ReviewCreate:
|
|
type: object
|
|
required:
|
|
- source
|
|
- review_text
|
|
properties:
|
|
external_id:
|
|
type: string
|
|
maxLength: 255
|
|
source:
|
|
type: string
|
|
enum: [google, yelp, tripadvisor, facebook, trustpilot, custom]
|
|
business_id:
|
|
type: string
|
|
maxLength: 255
|
|
author_name:
|
|
type: string
|
|
maxLength: 255
|
|
author_id:
|
|
type: string
|
|
maxLength: 255
|
|
review_text:
|
|
type: string
|
|
minLength: 1
|
|
star_rating:
|
|
type: number
|
|
minimum: 1.0
|
|
maximum: 5.0
|
|
review_date:
|
|
type: string
|
|
format: date
|
|
language_code:
|
|
type: string
|
|
maxLength: 5
|
|
raw_metadata:
|
|
type: object
|
|
|
|
Span:
|
|
type: object
|
|
required:
|
|
- span_id
|
|
- review_id
|
|
- span_text
|
|
- profile
|
|
- primary_code
|
|
- valence
|
|
- intensity
|
|
properties:
|
|
span_id:
|
|
type: string
|
|
format: uuid
|
|
description: Unique span identifier
|
|
review_id:
|
|
type: string
|
|
format: uuid
|
|
description: Parent review ID
|
|
span_text:
|
|
type: string
|
|
description: Classified text segment
|
|
example: "the service was slow"
|
|
char_start:
|
|
type: integer
|
|
minimum: 0
|
|
description: Start position in review text
|
|
char_end:
|
|
type: integer
|
|
minimum: 0
|
|
description: End position in review text
|
|
span_order:
|
|
type: integer
|
|
minimum: 1
|
|
description: Order within review
|
|
example: 2
|
|
profile:
|
|
$ref: '#/components/schemas/ImplementationProfile'
|
|
primary_code:
|
|
type: string
|
|
description: Primary URT classification code
|
|
example: "J1.02"
|
|
primary_tier:
|
|
type: integer
|
|
enum: [1, 2, 3]
|
|
description: "Code tier: 1=domain, 2=category, 3=subcode"
|
|
secondary_codes:
|
|
type: array
|
|
maxItems: 2
|
|
items:
|
|
type: string
|
|
description: Secondary URT codes (max 2)
|
|
example: ["A1.04"]
|
|
valence:
|
|
$ref: '#/components/schemas/Valence'
|
|
intensity:
|
|
$ref: '#/components/schemas/Intensity'
|
|
specificity:
|
|
$ref: '#/components/schemas/Specificity'
|
|
actionability:
|
|
$ref: '#/components/schemas/Actionability'
|
|
temporal:
|
|
$ref: '#/components/schemas/Temporal'
|
|
evidence:
|
|
$ref: '#/components/schemas/Evidence'
|
|
comparative:
|
|
$ref: '#/components/schemas/Comparative'
|
|
entity_reference:
|
|
type: string
|
|
maxLength: 255
|
|
description: Referenced product, person, or feature
|
|
location_reference:
|
|
type: string
|
|
maxLength: 255
|
|
description: Physical or logical location reference
|
|
causal_chain:
|
|
$ref: '#/components/schemas/CausalChain'
|
|
confidence_score:
|
|
type: number
|
|
format: float
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
description: Classification confidence (0.0-1.0)
|
|
example: 0.85
|
|
annotator_notes:
|
|
type: string
|
|
description: Annotator comments
|
|
annotation_source:
|
|
type: string
|
|
enum: [human, llm, hybrid, rule]
|
|
description: Classification source
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
|
|
SpanCreate:
|
|
type: object
|
|
required:
|
|
- review_id
|
|
- span_text
|
|
- primary_code
|
|
- valence
|
|
- intensity
|
|
properties:
|
|
review_id:
|
|
type: string
|
|
format: uuid
|
|
span_text:
|
|
type: string
|
|
minLength: 1
|
|
char_start:
|
|
type: integer
|
|
minimum: 0
|
|
char_end:
|
|
type: integer
|
|
minimum: 0
|
|
span_order:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
profile:
|
|
$ref: '#/components/schemas/ImplementationProfile'
|
|
primary_code:
|
|
type: string
|
|
pattern: '^[OPJEAVR]([1-4](\.[0-9]{2})?)?$'
|
|
secondary_codes:
|
|
type: array
|
|
maxItems: 2
|
|
items:
|
|
type: string
|
|
valence:
|
|
$ref: '#/components/schemas/Valence'
|
|
intensity:
|
|
$ref: '#/components/schemas/Intensity'
|
|
specificity:
|
|
$ref: '#/components/schemas/Specificity'
|
|
actionability:
|
|
$ref: '#/components/schemas/Actionability'
|
|
temporal:
|
|
$ref: '#/components/schemas/Temporal'
|
|
evidence:
|
|
$ref: '#/components/schemas/Evidence'
|
|
comparative:
|
|
$ref: '#/components/schemas/Comparative'
|
|
entity_reference:
|
|
type: string
|
|
maxLength: 255
|
|
location_reference:
|
|
type: string
|
|
maxLength: 255
|
|
causal_chain:
|
|
$ref: '#/components/schemas/CausalChainCreate'
|
|
confidence_score:
|
|
type: number
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
annotator_notes:
|
|
type: string
|
|
annotation_source:
|
|
type: string
|
|
enum: [human, llm, hybrid, rule]
|
|
|
|
SpanClassification:
|
|
type: object
|
|
description: Individual code assignment within a span
|
|
properties:
|
|
classification_id:
|
|
type: string
|
|
format: uuid
|
|
span_id:
|
|
type: string
|
|
format: uuid
|
|
code:
|
|
type: string
|
|
example: "J1.02"
|
|
code_tier:
|
|
type: integer
|
|
enum: [1, 2, 3]
|
|
is_primary:
|
|
type: boolean
|
|
classification_order:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 3
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Issue Schemas (C1 Lifecycle)
|
|
# -------------------------------------------------------------------------
|
|
|
|
Issue:
|
|
type: object
|
|
required:
|
|
- issue_id
|
|
- primary_subcode
|
|
- domain_code
|
|
- state
|
|
- span_count
|
|
- max_intensity
|
|
- priority_score
|
|
- confidence_score
|
|
properties:
|
|
issue_id:
|
|
type: string
|
|
pattern: '^ISSUE-[0-9]{4}-[0-9]{4}$'
|
|
description: Issue ID in format ISSUE-YYYY-NNNN
|
|
example: "ISSUE-2026-0042"
|
|
primary_subcode:
|
|
type: string
|
|
description: Primary URT subcode
|
|
example: "J1.02"
|
|
domain_code:
|
|
type: string
|
|
enum: [O, P, J, E, A, V, R]
|
|
description: Experience domain
|
|
entity_reference:
|
|
type: string
|
|
description: Aggregation entity
|
|
location_reference:
|
|
type: string
|
|
description: Aggregation location
|
|
state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
state_changed_at:
|
|
type: string
|
|
format: date-time
|
|
span_count:
|
|
type: integer
|
|
minimum: 1
|
|
description: Number of contributing spans
|
|
max_intensity:
|
|
$ref: '#/components/schemas/Intensity'
|
|
priority_score:
|
|
type: number
|
|
format: float
|
|
description: Calculated priority score
|
|
example: 8.75
|
|
confidence_score:
|
|
type: number
|
|
format: float
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
description: Issue confidence score
|
|
example: 0.85
|
|
owner_team:
|
|
type: string
|
|
description: Assigned team
|
|
example: "operations"
|
|
owner_individual:
|
|
type: string
|
|
description: Assigned individual
|
|
resolution_code:
|
|
type: string
|
|
description: Resolution code if resolved
|
|
resolution_notes:
|
|
type: string
|
|
decline_reason:
|
|
$ref: '#/components/schemas/DeclineReason'
|
|
causal_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Attributed causal codes
|
|
example: ["CD-O", "MG-P"]
|
|
reopen_count:
|
|
type: integer
|
|
minimum: 0
|
|
description: Times issue was reopened
|
|
verification_window_days:
|
|
type: integer
|
|
enum: [30, 60, 90]
|
|
default: 60
|
|
contributing_spans:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/SpanSummary'
|
|
description: Spans contributing to this issue
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
acknowledged_at:
|
|
type: string
|
|
format: date-time
|
|
work_started_at:
|
|
type: string
|
|
format: date-time
|
|
resolved_at:
|
|
type: string
|
|
format: date-time
|
|
verified_at:
|
|
type: string
|
|
format: date-time
|
|
declined_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
IssueCreate:
|
|
type: object
|
|
required:
|
|
- primary_subcode
|
|
- span_ids
|
|
properties:
|
|
primary_subcode:
|
|
type: string
|
|
pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
|
|
entity_reference:
|
|
type: string
|
|
location_reference:
|
|
type: string
|
|
span_ids:
|
|
type: array
|
|
minItems: 1
|
|
items:
|
|
type: string
|
|
format: uuid
|
|
description: Initial contributing span IDs
|
|
owner_team:
|
|
type: string
|
|
owner_individual:
|
|
type: string
|
|
verification_window_days:
|
|
type: integer
|
|
enum: [30, 60, 90]
|
|
default: 60
|
|
|
|
IssueState:
|
|
type: string
|
|
enum:
|
|
- DETECTED
|
|
- ACKNOWLEDGED
|
|
- IN_PROGRESS
|
|
- RESOLVED
|
|
- VERIFIED
|
|
- DECLINED
|
|
- REOPENED
|
|
- STALE
|
|
description: |
|
|
Issue lifecycle state per C1 framework:
|
|
- DETECTED: Initial detection from negative spans
|
|
- ACKNOWLEDGED: Team has acknowledged the issue
|
|
- IN_PROGRESS: Active work underway
|
|
- RESOLVED: Fix implemented, awaiting verification
|
|
- VERIFIED: Resolution confirmed via positive feedback
|
|
- DECLINED: Issue declined with reason code
|
|
- REOPENED: Issue recurred after resolution
|
|
- STALE: Issue aged out without resolution
|
|
|
|
IssueTransition:
|
|
type: object
|
|
required:
|
|
- to_state
|
|
properties:
|
|
to_state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
trigger_type:
|
|
type: string
|
|
enum: [manual, auto, span, sla]
|
|
default: manual
|
|
trigger_span_id:
|
|
type: string
|
|
format: uuid
|
|
description: Span that triggered transition (for span trigger)
|
|
notes:
|
|
type: string
|
|
description: Transition notes
|
|
resolution:
|
|
$ref: '#/components/schemas/IssueResolutionCreate'
|
|
|
|
IssueResolution:
|
|
type: object
|
|
properties:
|
|
resolution_id:
|
|
type: string
|
|
format: uuid
|
|
issue_id:
|
|
type: string
|
|
resolution_code:
|
|
type: string
|
|
example: "FIX-2026-0015"
|
|
resolution_type:
|
|
type: string
|
|
enum: [fix, workaround, policy_change, training, process_improvement, equipment, other]
|
|
resolution_summary:
|
|
type: string
|
|
example: "Implemented queue management system to reduce wait times"
|
|
resolution_details:
|
|
type: string
|
|
prevention_notes:
|
|
type: string
|
|
process_changes:
|
|
type: string
|
|
verified_by_span_id:
|
|
type: string
|
|
format: uuid
|
|
verification_method:
|
|
type: string
|
|
enum: [cr_b, positive_span, time_based]
|
|
description: How resolution was verified
|
|
resolved_by:
|
|
type: string
|
|
resolved_at:
|
|
type: string
|
|
format: date-time
|
|
verified_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
IssueResolutionCreate:
|
|
type: object
|
|
required:
|
|
- resolution_type
|
|
- resolution_summary
|
|
properties:
|
|
resolution_type:
|
|
type: string
|
|
enum: [fix, workaround, policy_change, training, process_improvement, equipment, other]
|
|
resolution_summary:
|
|
type: string
|
|
minLength: 10
|
|
resolution_details:
|
|
type: string
|
|
prevention_notes:
|
|
type: string
|
|
process_changes:
|
|
type: string
|
|
|
|
DeclineReason:
|
|
type: string
|
|
enum:
|
|
- DEC-DUP
|
|
- DEC-OOS
|
|
- DEC-INS
|
|
- DEC-NAR
|
|
- DEC-EXT
|
|
- DEC-POL
|
|
- DEC-OLD
|
|
description: |
|
|
Issue decline reason codes:
|
|
- DEC-DUP: Duplicate of existing issue
|
|
- DEC-OOS: Out of scope
|
|
- DEC-INS: Insufficient information
|
|
- DEC-NAR: Not actionable/reproducible
|
|
- DEC-EXT: External factor (beyond control)
|
|
- DEC-POL: Policy decision (intentional)
|
|
- DEC-OLD: Stale/outdated feedback
|
|
|
|
IssueStateHistory:
|
|
type: object
|
|
properties:
|
|
history_id:
|
|
type: string
|
|
format: uuid
|
|
issue_id:
|
|
type: string
|
|
from_state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
to_state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
trigger_type:
|
|
type: string
|
|
enum: [manual, auto, span, sla]
|
|
trigger_span_id:
|
|
type: string
|
|
format: uuid
|
|
actor_id:
|
|
type: string
|
|
actor_type:
|
|
type: string
|
|
enum: [user, system, rule]
|
|
notes:
|
|
type: string
|
|
transitioned_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# -------------------------------------------------------------------------
|
|
# URT Code Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
URTDomain:
|
|
type: object
|
|
properties:
|
|
domain_code:
|
|
type: string
|
|
enum: [O, P, J, E, A, V, R]
|
|
name:
|
|
type: string
|
|
example: "Journey"
|
|
description:
|
|
type: string
|
|
core_question:
|
|
type: string
|
|
example: "Is the experience smooth, timely, and friction-free?"
|
|
default_owner:
|
|
type: string
|
|
example: "Operations / Process"
|
|
categories:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTCategory'
|
|
|
|
URTCategory:
|
|
type: object
|
|
properties:
|
|
category_code:
|
|
type: string
|
|
pattern: '^[OPJEAVR][1-4]$'
|
|
example: "J1"
|
|
domain_code:
|
|
type: string
|
|
name:
|
|
type: string
|
|
example: "Timing"
|
|
definition:
|
|
type: string
|
|
example: "Speed, punctuality, and time management"
|
|
subcodes:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTSubcode'
|
|
|
|
URTSubcode:
|
|
type: object
|
|
properties:
|
|
subcode:
|
|
type: string
|
|
pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
|
|
example: "J1.02"
|
|
category_code:
|
|
type: string
|
|
domain_code:
|
|
type: string
|
|
name:
|
|
type: string
|
|
example: "Service Speed"
|
|
definition:
|
|
type: string
|
|
example: "Time for delivery/completion"
|
|
positive_example:
|
|
type: string
|
|
example: "Next day delivery"
|
|
negative_example:
|
|
type: string
|
|
example: "Took three weeks"
|
|
dont_confuse_with:
|
|
type: string
|
|
example: "J4.03"
|
|
dont_confuse_reason:
|
|
type: string
|
|
example: "J4.03 is resolution speed, J1.02 is service speed"
|
|
|
|
URTCausalCode:
|
|
type: object
|
|
properties:
|
|
causal_code:
|
|
type: string
|
|
pattern: '^(CD|MG|SY)-[A-Z]$'
|
|
example: "CD-O"
|
|
layer:
|
|
type: string
|
|
enum: [conditions, management, systemic]
|
|
layer_prefix:
|
|
type: string
|
|
enum: ["CD-", "MG-", "SY-"]
|
|
name:
|
|
type: string
|
|
example: "Operational"
|
|
definition:
|
|
type: string
|
|
example: "Understaffing, demand surge, time pressure"
|
|
|
|
CausalChain:
|
|
type: object
|
|
description: Root cause analysis chain (Full profile only)
|
|
properties:
|
|
chain_id:
|
|
type: string
|
|
format: uuid
|
|
span_id:
|
|
type: string
|
|
format: uuid
|
|
condition_code:
|
|
type: string
|
|
pattern: '^CD-[A-Z]$'
|
|
description: Conditions layer code
|
|
example: "CD-O"
|
|
management_code:
|
|
type: string
|
|
pattern: '^MG-[A-Z]$'
|
|
description: Management layer code
|
|
example: "MG-P"
|
|
systemic_code:
|
|
type: string
|
|
pattern: '^SY-[A-Z]$'
|
|
description: Systemic layer code
|
|
example: "SY-R"
|
|
chain_confidence:
|
|
type: number
|
|
format: float
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
analyst_notes:
|
|
type: string
|
|
|
|
CausalChainCreate:
|
|
type: object
|
|
properties:
|
|
condition_code:
|
|
type: string
|
|
pattern: '^CD-[A-Z]$'
|
|
management_code:
|
|
type: string
|
|
pattern: '^MG-[A-Z]$'
|
|
systemic_code:
|
|
type: string
|
|
pattern: '^SY-[A-Z]$'
|
|
chain_confidence:
|
|
type: number
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
analyst_notes:
|
|
type: string
|
|
|
|
CodeValidationRequest:
|
|
type: object
|
|
required:
|
|
- primary_code
|
|
properties:
|
|
primary_code:
|
|
type: string
|
|
secondary_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
profile:
|
|
$ref: '#/components/schemas/ImplementationProfile'
|
|
causal_chain:
|
|
$ref: '#/components/schemas/CausalChainCreate'
|
|
|
|
CodeValidationResult:
|
|
type: object
|
|
properties:
|
|
valid:
|
|
type: boolean
|
|
errors:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
field:
|
|
type: string
|
|
message:
|
|
type: string
|
|
warnings:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
field:
|
|
type: string
|
|
message:
|
|
type: string
|
|
normalized:
|
|
type: object
|
|
description: Normalized code values if valid
|
|
properties:
|
|
primary_code:
|
|
type: string
|
|
primary_tier:
|
|
type: integer
|
|
secondary_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Metadata Dimension Enums
|
|
# -------------------------------------------------------------------------
|
|
|
|
ImplementationProfile:
|
|
type: string
|
|
enum: [lite, core, standard, full]
|
|
default: standard
|
|
description: |
|
|
Implementation profile determining available fields:
|
|
- lite: 7 domains, valence only
|
|
- core: 28 categories, valence + intensity
|
|
- standard: 140 subcodes, all metadata
|
|
- full: 156 codes including causal analysis
|
|
|
|
Valence:
|
|
type: string
|
|
enum: ["V+", "V-", "V0", "V+-"]
|
|
description: |
|
|
Sentiment direction:
|
|
- V+: Positive (praise, satisfaction)
|
|
- V-: Negative (complaint, dissatisfaction)
|
|
- V0: Neutral (observation)
|
|
- V+-: Mixed (both positive and negative)
|
|
example: "V-"
|
|
|
|
Intensity:
|
|
type: string
|
|
enum: [I1, I2, I3]
|
|
description: |
|
|
Sentiment strength:
|
|
- I1: Mild
|
|
- I2: Moderate
|
|
- I3: Strong/emphatic
|
|
example: "I2"
|
|
|
|
Specificity:
|
|
type: string
|
|
enum: [S1, S2, S3]
|
|
description: |
|
|
Level of detail:
|
|
- S1: Vague (general impression)
|
|
- S2: Moderate (some details)
|
|
- S3: Specific (concrete details)
|
|
|
|
Actionability:
|
|
type: string
|
|
enum: [A1, A2, A3]
|
|
description: |
|
|
How actionable the feedback is:
|
|
- A1: Low (feeling only)
|
|
- A2: Medium (suggests area)
|
|
- A3: High (specific action)
|
|
|
|
Temporal:
|
|
type: string
|
|
enum: [TC, TR, TH, TF]
|
|
default: TC
|
|
description: |
|
|
Time frame reference:
|
|
- TC: Current (this visit)
|
|
- TR: Recent (recent pattern)
|
|
- TH: Historical (long-standing)
|
|
- TF: Future (expectations)
|
|
|
|
Evidence:
|
|
type: string
|
|
enum: [ES, EI, EC]
|
|
default: ES
|
|
description: |
|
|
Evidence type:
|
|
- ES: Stated (explicitly said)
|
|
- EI: Inferred (logically entailed)
|
|
- EC: Contextual (requires context)
|
|
|
|
Comparative:
|
|
type: string
|
|
enum: [CR-N, CR-B, CR-W, CR-S]
|
|
default: CR-N
|
|
description: |
|
|
Comparison to previous state:
|
|
- CR-N: None (no comparison)
|
|
- CR-B: Better (improvement)
|
|
- CR-W: Worse (decline)
|
|
- CR-S: Same (unchanged)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Analytics Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
DomainSummary:
|
|
type: object
|
|
properties:
|
|
domain_code:
|
|
type: string
|
|
domain_name:
|
|
type: string
|
|
default_owner:
|
|
type: string
|
|
total_spans:
|
|
type: integer
|
|
positive_spans:
|
|
type: integer
|
|
negative_spans:
|
|
type: integer
|
|
neutral_spans:
|
|
type: integer
|
|
mixed_spans:
|
|
type: integer
|
|
critical_spans:
|
|
type: integer
|
|
description: Spans with intensity I3
|
|
avg_confidence:
|
|
type: number
|
|
format: float
|
|
open_issues:
|
|
type: integer
|
|
last_span_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CategoryBreakdown:
|
|
type: object
|
|
properties:
|
|
category_code:
|
|
type: string
|
|
category_name:
|
|
type: string
|
|
domain_code:
|
|
type: string
|
|
domain_name:
|
|
type: string
|
|
total_spans:
|
|
type: integer
|
|
negative_count:
|
|
type: integer
|
|
positive_count:
|
|
type: integer
|
|
negative_pct:
|
|
type: number
|
|
format: float
|
|
total_issues:
|
|
type: integer
|
|
resolved_verified:
|
|
type: integer
|
|
avg_intensity:
|
|
type: number
|
|
format: float
|
|
|
|
TrendDataPoint:
|
|
type: object
|
|
properties:
|
|
period_start:
|
|
type: string
|
|
format: date
|
|
period_end:
|
|
type: string
|
|
format: date
|
|
domain_code:
|
|
type: string
|
|
category_code:
|
|
type: string
|
|
valence:
|
|
type: string
|
|
span_count:
|
|
type: integer
|
|
review_count:
|
|
type: integer
|
|
avg_intensity:
|
|
type: number
|
|
format: float
|
|
cr_better_count:
|
|
type: integer
|
|
cr_worse_count:
|
|
type: integer
|
|
cr_same_count:
|
|
type: integer
|
|
|
|
AnalyticsSummaryResponse:
|
|
type: object
|
|
properties:
|
|
generated_at:
|
|
type: string
|
|
format: date-time
|
|
period:
|
|
type: object
|
|
properties:
|
|
start:
|
|
type: string
|
|
format: date
|
|
end:
|
|
type: string
|
|
format: date
|
|
domains:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/DomainSummary'
|
|
categories:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/CategoryBreakdown'
|
|
totals:
|
|
type: object
|
|
properties:
|
|
total_reviews:
|
|
type: integer
|
|
total_spans:
|
|
type: integer
|
|
total_issues:
|
|
type: integer
|
|
open_issues:
|
|
type: integer
|
|
avg_resolution_hours:
|
|
type: number
|
|
format: float
|
|
|
|
TrendsResponse:
|
|
type: object
|
|
properties:
|
|
generated_at:
|
|
type: string
|
|
format: date-time
|
|
granularity:
|
|
type: string
|
|
enum: [day, week, month]
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/TrendDataPoint'
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Routing Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
RoutingRequest:
|
|
type: object
|
|
required:
|
|
- primary_code
|
|
- valence
|
|
- intensity
|
|
properties:
|
|
primary_code:
|
|
type: string
|
|
secondary_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
valence:
|
|
$ref: '#/components/schemas/Valence'
|
|
intensity:
|
|
$ref: '#/components/schemas/Intensity'
|
|
causal_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
RoutingResponse:
|
|
type: object
|
|
properties:
|
|
primary_owner:
|
|
type: object
|
|
properties:
|
|
team:
|
|
type: string
|
|
example: "operations"
|
|
escalation:
|
|
type: string
|
|
example: "operations_director"
|
|
co_owners:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
team:
|
|
type: string
|
|
reason:
|
|
type: string
|
|
sla:
|
|
type: object
|
|
properties:
|
|
priority:
|
|
type: string
|
|
enum: [critical, high, normal, low]
|
|
initial_response_hours:
|
|
type: integer
|
|
resolution_hours:
|
|
type: integer
|
|
update_frequency_hours:
|
|
type: integer
|
|
auto_escalate:
|
|
type: boolean
|
|
notifications:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
channel:
|
|
type: string
|
|
enum: [email, slack]
|
|
target:
|
|
type: string
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Classification Request/Response
|
|
# -------------------------------------------------------------------------
|
|
|
|
ClassifyReviewRequest:
|
|
type: object
|
|
required:
|
|
- profile
|
|
properties:
|
|
profile:
|
|
$ref: '#/components/schemas/ImplementationProfile'
|
|
options:
|
|
type: object
|
|
properties:
|
|
split_spans:
|
|
type: boolean
|
|
default: true
|
|
description: Automatically split review into spans
|
|
include_causal:
|
|
type: boolean
|
|
default: false
|
|
description: Include causal analysis (Full profile)
|
|
confidence_threshold:
|
|
type: number
|
|
minimum: 0.0
|
|
maximum: 1.0
|
|
default: 0.7
|
|
description: Minimum confidence for auto-classification
|
|
|
|
ClassifyReviewResponse:
|
|
type: object
|
|
properties:
|
|
review_id:
|
|
type: string
|
|
format: uuid
|
|
status:
|
|
type: string
|
|
enum: [completed, partial, pending_review]
|
|
spans:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Span'
|
|
issues_created:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: IDs of any issues created
|
|
processing_time_ms:
|
|
type: integer
|
|
|
|
BatchClassifyRequest:
|
|
type: object
|
|
required:
|
|
- spans
|
|
properties:
|
|
profile:
|
|
$ref: '#/components/schemas/ImplementationProfile'
|
|
spans:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 100
|
|
items:
|
|
$ref: '#/components/schemas/SpanCreate'
|
|
|
|
BatchClassifyResponse:
|
|
type: object
|
|
properties:
|
|
total:
|
|
type: integer
|
|
successful:
|
|
type: integer
|
|
failed:
|
|
type: integer
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
index:
|
|
type: integer
|
|
span:
|
|
$ref: '#/components/schemas/Span'
|
|
error:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Common Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
SpanSummary:
|
|
type: object
|
|
properties:
|
|
span_id:
|
|
type: string
|
|
format: uuid
|
|
span_text:
|
|
type: string
|
|
primary_code:
|
|
type: string
|
|
valence:
|
|
type: string
|
|
intensity:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
Pagination:
|
|
type: object
|
|
properties:
|
|
cursor:
|
|
type: string
|
|
description: Cursor for next page
|
|
has_more:
|
|
type: boolean
|
|
total:
|
|
type: integer
|
|
description: Total count (when available)
|
|
limit:
|
|
type: integer
|
|
|
|
PaginatedResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: {}
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Error Schemas
|
|
# -------------------------------------------------------------------------
|
|
|
|
Error:
|
|
type: object
|
|
required:
|
|
- code
|
|
- message
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: Machine-readable error code
|
|
message:
|
|
type: string
|
|
description: Human-readable error message
|
|
details:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Additional error context
|
|
request_id:
|
|
type: string
|
|
description: Request ID for debugging
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
required:
|
|
- error
|
|
properties:
|
|
error:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
ValidationError:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
example: "VALIDATION_ERROR"
|
|
message:
|
|
type: string
|
|
example: "Request validation failed"
|
|
field_errors:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
field:
|
|
type: string
|
|
message:
|
|
type: string
|
|
code:
|
|
type: string
|
|
|
|
HealthStatus:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [healthy, degraded, unhealthy]
|
|
version:
|
|
type: string
|
|
example: "1.0.0"
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
components:
|
|
type: object
|
|
properties:
|
|
database:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
latency_ms:
|
|
type: integer
|
|
cache:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
latency_ms:
|
|
type: integer
|
|
classifier:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
model_version:
|
|
type: string
|
|
|
|
# ===========================================================================
|
|
# RESPONSE HEADERS
|
|
# ===========================================================================
|
|
headers:
|
|
X-RateLimit-Limit:
|
|
description: Maximum requests allowed per window
|
|
schema:
|
|
type: integer
|
|
example: 1000
|
|
X-RateLimit-Remaining:
|
|
description: Requests remaining in current window
|
|
schema:
|
|
type: integer
|
|
example: 998
|
|
X-RateLimit-Reset:
|
|
description: Unix timestamp when rate limit resets
|
|
schema:
|
|
type: integer
|
|
example: 1706054400
|
|
X-Request-Id:
|
|
description: Unique request identifier for debugging
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
|
|
# ===========================================================================
|
|
# COMMON PARAMETERS
|
|
# ===========================================================================
|
|
parameters:
|
|
ReviewId:
|
|
name: review_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
description: Review UUID
|
|
SpanId:
|
|
name: span_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
description: Span UUID
|
|
IssueId:
|
|
name: issue_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^ISSUE-[0-9]{4}-[0-9]{4}$'
|
|
description: Issue ID (format ISSUE-YYYY-NNNN)
|
|
Cursor:
|
|
name: cursor
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Pagination cursor
|
|
Limit:
|
|
name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
description: Results per page
|
|
DomainFilter:
|
|
name: domain
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [O, P, J, E, A, V, R]
|
|
description: Filter by domain code
|
|
ValenceFilter:
|
|
name: valence
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: ["V+", "V-", "V0", "V+-"]
|
|
description: Filter by valence
|
|
IntensityFilter:
|
|
name: intensity
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [I1, I2, I3]
|
|
description: Filter by intensity
|
|
DateFrom:
|
|
name: from
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: Start date (inclusive)
|
|
DateTo:
|
|
name: to
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: End date (inclusive)
|
|
|
|
# ===========================================================================
|
|
# COMMON RESPONSES
|
|
# ===========================================================================
|
|
responses:
|
|
BadRequest:
|
|
description: Bad request - validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ValidationError'
|
|
example:
|
|
code: "VALIDATION_ERROR"
|
|
message: "Request validation failed"
|
|
field_errors:
|
|
- field: "primary_code"
|
|
message: "Invalid URT code format"
|
|
code: "INVALID_CODE_FORMAT"
|
|
Unauthorized:
|
|
description: Authentication required
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "UNAUTHORIZED"
|
|
message: "Authentication required"
|
|
Forbidden:
|
|
description: Insufficient permissions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "FORBIDDEN"
|
|
message: "Insufficient permissions for this operation"
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "NOT_FOUND"
|
|
message: "Resource not found"
|
|
Conflict:
|
|
description: Conflict - invalid state transition
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "INVALID_TRANSITION"
|
|
message: "Cannot transition from VERIFIED to IN_PROGRESS"
|
|
RateLimited:
|
|
description: Rate limit exceeded
|
|
headers:
|
|
X-RateLimit-Limit:
|
|
$ref: '#/components/headers/X-RateLimit-Limit'
|
|
X-RateLimit-Remaining:
|
|
$ref: '#/components/headers/X-RateLimit-Remaining'
|
|
X-RateLimit-Reset:
|
|
$ref: '#/components/headers/X-RateLimit-Reset'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "RATE_LIMITED"
|
|
message: "Rate limit exceeded. Retry after reset."
|
|
InternalError:
|
|
description: Internal server error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error:
|
|
code: "INTERNAL_ERROR"
|
|
message: "An unexpected error occurred"
|
|
request_id: "req_abc123"
|
|
|
|
# =============================================================================
|
|
# PATHS
|
|
# =============================================================================
|
|
|
|
paths:
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# REVIEWS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/reviews:
|
|
get:
|
|
tags: [Reviews]
|
|
summary: List reviews
|
|
description: Retrieve paginated list of reviews with optional filters
|
|
operationId: listReviews
|
|
parameters:
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/DateFrom'
|
|
- $ref: '#/components/parameters/DateTo'
|
|
- name: source
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by source platform
|
|
- name: business_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by business ID
|
|
- name: has_spans
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
description: Filter by classification status
|
|
responses:
|
|
'200':
|
|
description: List of reviews
|
|
headers:
|
|
X-RateLimit-Limit:
|
|
$ref: '#/components/headers/X-RateLimit-Limit'
|
|
X-RateLimit-Remaining:
|
|
$ref: '#/components/headers/X-RateLimit-Remaining'
|
|
X-RateLimit-Reset:
|
|
$ref: '#/components/headers/X-RateLimit-Reset'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Review'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
example:
|
|
data:
|
|
- review_id: "550e8400-e29b-41d4-a716-446655440000"
|
|
source: "google"
|
|
review_text: "Great food but slow service"
|
|
star_rating: 3.5
|
|
review_date: "2026-01-15"
|
|
span_count: 2
|
|
pagination:
|
|
cursor: "eyJpZCI6MTAwfQ"
|
|
has_more: true
|
|
limit: 20
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
post:
|
|
tags: [Reviews]
|
|
summary: Create review
|
|
description: Create a new review record
|
|
operationId: createReview
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReviewCreate'
|
|
example:
|
|
source: "google"
|
|
business_id: "BUS-2026-0001"
|
|
author_name: "Jane D."
|
|
review_text: "Excellent service, very professional staff. The wait time could be improved."
|
|
star_rating: 4.0
|
|
review_date: "2026-01-20"
|
|
language_code: "en"
|
|
responses:
|
|
'201':
|
|
description: Review created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Review'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/reviews/{review_id}:
|
|
get:
|
|
tags: [Reviews]
|
|
summary: Get review
|
|
description: Retrieve a single review by ID
|
|
operationId: getReview
|
|
parameters:
|
|
- $ref: '#/components/parameters/ReviewId'
|
|
- name: include_spans
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
default: true
|
|
description: Include classified spans
|
|
responses:
|
|
'200':
|
|
description: Review details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Review'
|
|
- type: object
|
|
properties:
|
|
spans:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Span'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
patch:
|
|
tags: [Reviews]
|
|
summary: Update review
|
|
description: Update review metadata
|
|
operationId: updateReview
|
|
parameters:
|
|
- $ref: '#/components/parameters/ReviewId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
business_id:
|
|
type: string
|
|
star_rating:
|
|
type: number
|
|
review_date:
|
|
type: string
|
|
format: date
|
|
language_code:
|
|
type: string
|
|
raw_metadata:
|
|
type: object
|
|
responses:
|
|
'200':
|
|
description: Review updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Review'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
delete:
|
|
tags: [Reviews]
|
|
summary: Delete review
|
|
description: Soft delete a review and its spans
|
|
operationId: deleteReview
|
|
parameters:
|
|
- $ref: '#/components/parameters/ReviewId'
|
|
responses:
|
|
'204':
|
|
description: Review deleted
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/reviews/{review_id}/classify:
|
|
post:
|
|
tags: [Reviews]
|
|
summary: Classify review
|
|
description: |
|
|
Submit a review for automatic classification. The review will be split into
|
|
spans and classified according to the specified profile.
|
|
operationId: classifyReview
|
|
parameters:
|
|
- $ref: '#/components/parameters/ReviewId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ClassifyReviewRequest'
|
|
example:
|
|
profile: "standard"
|
|
options:
|
|
split_spans: true
|
|
include_causal: false
|
|
confidence_threshold: 0.7
|
|
responses:
|
|
'200':
|
|
description: Classification complete
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ClassifyReviewResponse'
|
|
example:
|
|
review_id: "550e8400-e29b-41d4-a716-446655440000"
|
|
status: "completed"
|
|
spans:
|
|
- span_id: "660e8400-e29b-41d4-a716-446655440001"
|
|
span_text: "Great food"
|
|
primary_code: "O2.01"
|
|
valence: "V+"
|
|
intensity: "I2"
|
|
- span_id: "660e8400-e29b-41d4-a716-446655440002"
|
|
span_text: "slow service"
|
|
primary_code: "J1.02"
|
|
valence: "V-"
|
|
intensity: "I2"
|
|
issues_created: []
|
|
processing_time_ms: 342
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SPANS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/spans:
|
|
get:
|
|
tags: [Spans]
|
|
summary: List spans
|
|
description: Retrieve paginated list of classified spans
|
|
operationId: listSpans
|
|
parameters:
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/DomainFilter'
|
|
- $ref: '#/components/parameters/ValenceFilter'
|
|
- $ref: '#/components/parameters/IntensityFilter'
|
|
- $ref: '#/components/parameters/DateFrom'
|
|
- $ref: '#/components/parameters/DateTo'
|
|
- name: review_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
description: Filter by review ID
|
|
- name: primary_code
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by primary code (exact or prefix)
|
|
- name: comparative
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [CR-N, CR-B, CR-W, CR-S]
|
|
description: Filter by comparative reference
|
|
responses:
|
|
'200':
|
|
description: List of spans
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Span'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
post:
|
|
tags: [Spans]
|
|
summary: Create span
|
|
description: Create a new classified span
|
|
operationId: createSpan
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SpanCreate'
|
|
example:
|
|
review_id: "550e8400-e29b-41d4-a716-446655440000"
|
|
span_text: "the service was slow"
|
|
char_start: 15
|
|
char_end: 35
|
|
span_order: 2
|
|
profile: "standard"
|
|
primary_code: "J1.02"
|
|
secondary_codes: ["A1.04"]
|
|
valence: "V-"
|
|
intensity: "I2"
|
|
specificity: "S2"
|
|
actionability: "A2"
|
|
temporal: "TC"
|
|
evidence: "ES"
|
|
comparative: "CR-N"
|
|
confidence_score: 0.85
|
|
annotation_source: "llm"
|
|
responses:
|
|
'201':
|
|
description: Span created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Span'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
/spans/{span_id}:
|
|
get:
|
|
tags: [Spans]
|
|
summary: Get span
|
|
description: Retrieve a single span by ID
|
|
operationId: getSpan
|
|
parameters:
|
|
- $ref: '#/components/parameters/SpanId'
|
|
responses:
|
|
'200':
|
|
description: Span details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Span'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
patch:
|
|
tags: [Spans]
|
|
summary: Update span
|
|
description: Update span classification
|
|
operationId: updateSpan
|
|
parameters:
|
|
- $ref: '#/components/parameters/SpanId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
primary_code:
|
|
type: string
|
|
secondary_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
valence:
|
|
$ref: '#/components/schemas/Valence'
|
|
intensity:
|
|
$ref: '#/components/schemas/Intensity'
|
|
specificity:
|
|
$ref: '#/components/schemas/Specificity'
|
|
actionability:
|
|
$ref: '#/components/schemas/Actionability'
|
|
temporal:
|
|
$ref: '#/components/schemas/Temporal'
|
|
evidence:
|
|
$ref: '#/components/schemas/Evidence'
|
|
comparative:
|
|
$ref: '#/components/schemas/Comparative'
|
|
annotator_notes:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Span updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Span'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
delete:
|
|
tags: [Spans]
|
|
summary: Delete span
|
|
description: Soft delete a span
|
|
operationId: deleteSpan
|
|
parameters:
|
|
- $ref: '#/components/parameters/SpanId'
|
|
responses:
|
|
'204':
|
|
description: Span deleted
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/spans/batch:
|
|
post:
|
|
tags: [Spans]
|
|
summary: Batch create spans
|
|
description: Create multiple spans in a single request (max 100)
|
|
operationId: batchCreateSpans
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BatchClassifyRequest'
|
|
responses:
|
|
'200':
|
|
description: Batch results
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BatchClassifyResponse'
|
|
example:
|
|
total: 5
|
|
successful: 4
|
|
failed: 1
|
|
results:
|
|
- index: 0
|
|
span:
|
|
span_id: "770e8400-e29b-41d4-a716-446655440001"
|
|
primary_code: "J1.02"
|
|
- index: 1
|
|
error:
|
|
code: "INVALID_CODE"
|
|
message: "Invalid primary_code: X9.99"
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ISSUES
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/issues:
|
|
get:
|
|
tags: [Issues]
|
|
summary: List issues
|
|
description: Retrieve paginated list of issues
|
|
operationId: listIssues
|
|
parameters:
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/DomainFilter'
|
|
- $ref: '#/components/parameters/DateFrom'
|
|
- $ref: '#/components/parameters/DateTo'
|
|
- name: state
|
|
in: query
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/IssueState'
|
|
style: form
|
|
explode: true
|
|
description: Filter by state(s)
|
|
- name: owner_team
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by owner team
|
|
- name: priority_min
|
|
in: query
|
|
schema:
|
|
type: number
|
|
description: Minimum priority score
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [priority_desc, created_desc, created_asc]
|
|
default: priority_desc
|
|
description: Sort order
|
|
responses:
|
|
'200':
|
|
description: List of issues
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Issue'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
post:
|
|
tags: [Issues]
|
|
summary: Create issue
|
|
description: Manually create an issue from spans
|
|
operationId: createIssue
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/IssueCreate'
|
|
example:
|
|
primary_subcode: "J1.02"
|
|
entity_reference: "Downtown location"
|
|
span_ids:
|
|
- "660e8400-e29b-41d4-a716-446655440001"
|
|
- "660e8400-e29b-41d4-a716-446655440002"
|
|
owner_team: "operations"
|
|
responses:
|
|
'201':
|
|
description: Issue created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Issue'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
/issues/{issue_id}:
|
|
get:
|
|
tags: [Issues]
|
|
summary: Get issue
|
|
description: Retrieve issue details including history
|
|
operationId: getIssue
|
|
parameters:
|
|
- $ref: '#/components/parameters/IssueId'
|
|
- name: include_history
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
description: Include state transition history
|
|
- name: include_spans
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
default: true
|
|
description: Include contributing spans
|
|
responses:
|
|
'200':
|
|
description: Issue details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Issue'
|
|
- type: object
|
|
properties:
|
|
state_history:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/IssueStateHistory'
|
|
resolution:
|
|
$ref: '#/components/schemas/IssueResolution'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
patch:
|
|
tags: [Issues]
|
|
summary: Update issue
|
|
description: Update issue metadata (not state - use transition endpoint)
|
|
operationId: updateIssue
|
|
parameters:
|
|
- $ref: '#/components/parameters/IssueId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
owner_team:
|
|
type: string
|
|
owner_individual:
|
|
type: string
|
|
entity_reference:
|
|
type: string
|
|
location_reference:
|
|
type: string
|
|
causal_codes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
verification_window_days:
|
|
type: integer
|
|
enum: [30, 60, 90]
|
|
responses:
|
|
'200':
|
|
description: Issue updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Issue'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/issues/{issue_id}/transition:
|
|
post:
|
|
tags: [Issues]
|
|
summary: Transition issue state
|
|
description: |
|
|
Transition issue to a new state according to the C1 lifecycle state machine.
|
|
|
|
Valid transitions:
|
|
- DETECTED -> ACKNOWLEDGED, DECLINED
|
|
- ACKNOWLEDGED -> IN_PROGRESS, DECLINED
|
|
- IN_PROGRESS -> RESOLVED, DECLINED
|
|
- RESOLVED -> VERIFIED, REOPENED
|
|
- DECLINED -> (terminal)
|
|
- VERIFIED -> REOPENED
|
|
- REOPENED -> IN_PROGRESS, DECLINED
|
|
- STALE -> ACKNOWLEDGED, DECLINED
|
|
operationId: transitionIssue
|
|
parameters:
|
|
- $ref: '#/components/parameters/IssueId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/IssueTransition'
|
|
examples:
|
|
acknowledge:
|
|
summary: Acknowledge issue
|
|
value:
|
|
to_state: "ACKNOWLEDGED"
|
|
notes: "Assigned to operations team for investigation"
|
|
resolve:
|
|
summary: Resolve with details
|
|
value:
|
|
to_state: "RESOLVED"
|
|
notes: "Implemented queue management system"
|
|
resolution:
|
|
resolution_type: "process_improvement"
|
|
resolution_summary: "Added digital queue display and SMS notifications"
|
|
decline:
|
|
summary: Decline issue
|
|
value:
|
|
to_state: "DECLINED"
|
|
notes: "External factor - weather related"
|
|
responses:
|
|
'200':
|
|
description: Transition successful
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Issue'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
|
|
/issues/{issue_id}/spans:
|
|
get:
|
|
tags: [Issues]
|
|
summary: Get issue spans
|
|
description: List all spans contributing to an issue
|
|
operationId: getIssueSpans
|
|
parameters:
|
|
- $ref: '#/components/parameters/IssueId'
|
|
responses:
|
|
'200':
|
|
description: Contributing spans
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
issue_id:
|
|
type: string
|
|
spans:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Span'
|
|
|
|
post:
|
|
tags: [Issues]
|
|
summary: Link span to issue
|
|
description: Add a span as a contributor to an existing issue
|
|
operationId: linkSpanToIssue
|
|
parameters:
|
|
- $ref: '#/components/parameters/IssueId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- span_id
|
|
properties:
|
|
span_id:
|
|
type: string
|
|
format: uuid
|
|
link_type:
|
|
type: string
|
|
enum: [contributor, verification, reopen_trigger]
|
|
default: contributor
|
|
responses:
|
|
'200':
|
|
description: Span linked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Issue'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CODES
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/codes:
|
|
get:
|
|
tags: [Codes]
|
|
summary: List all codes
|
|
description: Retrieve the full URT code registry
|
|
operationId: listCodes
|
|
parameters:
|
|
- name: tier
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
enum: [1, 2, 3]
|
|
description: Filter by tier (1=domain, 2=category, 3=subcode)
|
|
- name: domain
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [O, P, J, E, A, V, R]
|
|
description: Filter by domain
|
|
- name: include_causal
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
description: Include causal codes
|
|
responses:
|
|
'200':
|
|
description: Code registry
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
version:
|
|
type: string
|
|
example: "5.1"
|
|
domains:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTDomain'
|
|
causal_codes:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTCausalCode'
|
|
|
|
/codes/domains:
|
|
get:
|
|
tags: [Codes]
|
|
summary: List domains
|
|
description: Retrieve all 7 experience domains
|
|
operationId: listDomains
|
|
responses:
|
|
'200':
|
|
description: Domain list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
domains:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTDomain'
|
|
|
|
/codes/domains/{domain_code}:
|
|
get:
|
|
tags: [Codes]
|
|
summary: Get domain
|
|
description: Retrieve domain with its categories and subcodes
|
|
operationId: getDomain
|
|
parameters:
|
|
- name: domain_code
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
enum: [O, P, J, E, A, V, R]
|
|
responses:
|
|
'200':
|
|
description: Domain details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/URTDomain'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/codes/categories/{category_code}:
|
|
get:
|
|
tags: [Codes]
|
|
summary: Get category
|
|
description: Retrieve category with its subcodes
|
|
operationId: getCategory
|
|
parameters:
|
|
- name: category_code
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[OPJEAVR][1-4]$'
|
|
responses:
|
|
'200':
|
|
description: Category details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/URTCategory'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/codes/subcodes/{subcode}:
|
|
get:
|
|
tags: [Codes]
|
|
summary: Get subcode
|
|
description: Retrieve subcode details with disambiguation guidance
|
|
operationId: getSubcode
|
|
parameters:
|
|
- name: subcode
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
|
|
responses:
|
|
'200':
|
|
description: Subcode details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/URTSubcode'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/codes/causal:
|
|
get:
|
|
tags: [Codes]
|
|
summary: List causal codes
|
|
description: Retrieve all 16 causal codes across 3 layers
|
|
operationId: listCausalCodes
|
|
parameters:
|
|
- name: layer
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [conditions, management, systemic]
|
|
description: Filter by layer
|
|
responses:
|
|
'200':
|
|
description: Causal code list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
causal_codes:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/URTCausalCode'
|
|
|
|
/codes/validate:
|
|
post:
|
|
tags: [Codes]
|
|
summary: Validate code combination
|
|
description: |
|
|
Validate that a code combination is valid for the specified profile.
|
|
Returns normalized codes and any warnings.
|
|
operationId: validateCodes
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CodeValidationRequest'
|
|
example:
|
|
primary_code: "J1.02"
|
|
secondary_codes: ["A1.04"]
|
|
profile: "standard"
|
|
responses:
|
|
'200':
|
|
description: Validation result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CodeValidationResult'
|
|
examples:
|
|
valid:
|
|
summary: Valid combination
|
|
value:
|
|
valid: true
|
|
errors: []
|
|
warnings: []
|
|
normalized:
|
|
primary_code: "J1.02"
|
|
primary_tier: 3
|
|
secondary_codes: ["A1.04"]
|
|
invalid:
|
|
summary: Invalid code
|
|
value:
|
|
valid: false
|
|
errors:
|
|
- code: "INVALID_CODE"
|
|
field: "primary_code"
|
|
message: "Code X9.99 does not exist"
|
|
warnings: []
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ANALYTICS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/analytics/summary:
|
|
get:
|
|
tags: [Analytics]
|
|
summary: Get analytics summary
|
|
description: Retrieve aggregated metrics by domain and category
|
|
operationId: getAnalyticsSummary
|
|
parameters:
|
|
- $ref: '#/components/parameters/DateFrom'
|
|
- $ref: '#/components/parameters/DateTo'
|
|
- name: business_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by business
|
|
responses:
|
|
'200':
|
|
description: Analytics summary
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalyticsSummaryResponse'
|
|
example:
|
|
generated_at: "2026-01-23T10:30:00Z"
|
|
period:
|
|
start: "2026-01-01"
|
|
end: "2026-01-23"
|
|
domains:
|
|
- domain_code: "J"
|
|
domain_name: "Journey"
|
|
total_spans: 145
|
|
negative_spans: 98
|
|
positive_spans: 42
|
|
open_issues: 12
|
|
totals:
|
|
total_reviews: 523
|
|
total_spans: 847
|
|
total_issues: 45
|
|
open_issues: 28
|
|
avg_resolution_hours: 36.5
|
|
|
|
/analytics/trends:
|
|
get:
|
|
tags: [Analytics]
|
|
summary: Get trend data
|
|
description: Retrieve time-series metrics for trend analysis
|
|
operationId: getAnalyticsTrends
|
|
parameters:
|
|
- $ref: '#/components/parameters/DateFrom'
|
|
- $ref: '#/components/parameters/DateTo'
|
|
- $ref: '#/components/parameters/DomainFilter'
|
|
- name: granularity
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [day, week, month]
|
|
default: week
|
|
description: Time period granularity
|
|
- name: metrics
|
|
in: query
|
|
schema:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [span_count, issue_count, avg_intensity, cr_signals]
|
|
style: form
|
|
explode: true
|
|
description: Metrics to include
|
|
responses:
|
|
'200':
|
|
description: Trend data
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrendsResponse'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ROUTING
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/routing/resolve:
|
|
post:
|
|
tags: [Routing]
|
|
summary: Resolve owner routing
|
|
description: |
|
|
Determine the responsible team and SLA for a classification based on
|
|
the B3 owner routing matrix.
|
|
operationId: resolveRouting
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoutingRequest'
|
|
example:
|
|
primary_code: "E4.01"
|
|
secondary_codes: []
|
|
valence: "V-"
|
|
intensity: "I3"
|
|
responses:
|
|
'200':
|
|
description: Routing resolution
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoutingResponse'
|
|
example:
|
|
primary_owner:
|
|
team: "safety"
|
|
escalation: "safety_officer"
|
|
co_owners: []
|
|
sla:
|
|
priority: "critical"
|
|
initial_response_hours: 1
|
|
resolution_hours: 4
|
|
update_frequency_hours: 1
|
|
auto_escalate: true
|
|
notifications:
|
|
- channel: "slack"
|
|
target: "#safety"
|
|
- channel: "email"
|
|
target: "safety@example.com"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HEALTH
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/health:
|
|
get:
|
|
tags: [Health]
|
|
summary: Health check
|
|
description: Check API and component health status
|
|
operationId: healthCheck
|
|
security: []
|
|
responses:
|
|
'200':
|
|
description: System healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/HealthStatus'
|
|
example:
|
|
status: "healthy"
|
|
version: "1.0.0"
|
|
timestamp: "2026-01-23T10:30:00Z"
|
|
components:
|
|
database:
|
|
status: "healthy"
|
|
latency_ms: 5
|
|
cache:
|
|
status: "healthy"
|
|
latency_ms: 1
|
|
classifier:
|
|
status: "healthy"
|
|
model_version: "urt-classifier-v5.1"
|
|
'503':
|
|
description: System unhealthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/HealthStatus'
|
|
example:
|
|
status: "unhealthy"
|
|
version: "1.0.0"
|
|
timestamp: "2026-01-23T10:30:00Z"
|
|
components:
|
|
database:
|
|
status: "unhealthy"
|
|
latency_ms: null
|
|
cache:
|
|
status: "healthy"
|
|
latency_ms: 1
|
|
|
|
# =============================================================================
|
|
# WEBHOOKS
|
|
# =============================================================================
|
|
|
|
webhooks:
|
|
issueStateChanged:
|
|
post:
|
|
summary: Issue state changed
|
|
description: |
|
|
Triggered when an issue transitions to a new state.
|
|
|
|
Subscribe via the webhook configuration endpoint (not shown).
|
|
operationId: onIssueStateChanged
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
event_type:
|
|
type: string
|
|
enum: [issue.state_changed]
|
|
event_id:
|
|
type: string
|
|
format: uuid
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
data:
|
|
type: object
|
|
properties:
|
|
issue_id:
|
|
type: string
|
|
from_state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
to_state:
|
|
$ref: '#/components/schemas/IssueState'
|
|
trigger_type:
|
|
type: string
|
|
actor_id:
|
|
type: string
|
|
issue:
|
|
$ref: '#/components/schemas/Issue'
|
|
example:
|
|
event_type: "issue.state_changed"
|
|
event_id: "evt_abc123"
|
|
timestamp: "2026-01-23T10:30:00Z"
|
|
data:
|
|
issue_id: "ISSUE-2026-0042"
|
|
from_state: "IN_PROGRESS"
|
|
to_state: "RESOLVED"
|
|
trigger_type: "manual"
|
|
actor_id: "user_123"
|
|
issue:
|
|
issue_id: "ISSUE-2026-0042"
|
|
state: "RESOLVED"
|
|
primary_subcode: "J1.02"
|
|
responses:
|
|
'200':
|
|
description: Webhook received
|
|
|
|
issueCreated:
|
|
post:
|
|
summary: Issue created
|
|
description: Triggered when a new issue is detected or created
|
|
operationId: onIssueCreated
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
event_type:
|
|
type: string
|
|
enum: [issue.created]
|
|
event_id:
|
|
type: string
|
|
format: uuid
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
data:
|
|
type: object
|
|
properties:
|
|
issue:
|
|
$ref: '#/components/schemas/Issue'
|
|
contributing_spans:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/SpanSummary'
|
|
responses:
|
|
'200':
|
|
description: Webhook received
|
|
|
|
spanClassified:
|
|
post:
|
|
summary: Span classified
|
|
description: Triggered when a new span is classified with negative valence
|
|
operationId: onSpanClassified
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
event_type:
|
|
type: string
|
|
enum: [span.classified]
|
|
event_id:
|
|
type: string
|
|
format: uuid
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
data:
|
|
type: object
|
|
properties:
|
|
span:
|
|
$ref: '#/components/schemas/Span'
|
|
review:
|
|
$ref: '#/components/schemas/Review'
|
|
routing:
|
|
$ref: '#/components/schemas/RoutingResponse'
|
|
responses:
|
|
'200':
|
|
description: Webhook received
|
|
|
|
slaBreach:
|
|
post:
|
|
summary: SLA breach warning
|
|
description: Triggered when an issue approaches or exceeds SLA thresholds
|
|
operationId: onSlaBreach
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
event_type:
|
|
type: string
|
|
enum: [sla.warning, sla.breach, sla.critical]
|
|
event_id:
|
|
type: string
|
|
format: uuid
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
data:
|
|
type: object
|
|
properties:
|
|
issue_id:
|
|
type: string
|
|
issue:
|
|
$ref: '#/components/schemas/Issue'
|
|
sla_type:
|
|
type: string
|
|
enum: [initial_response, resolution, update]
|
|
threshold_percent:
|
|
type: integer
|
|
hours_elapsed:
|
|
type: number
|
|
hours_allowed:
|
|
type: number
|
|
responses:
|
|
'200':
|
|
description: Webhook received
|
|
|
|
# =============================================================================
|
|
# END OF SPECIFICATION
|
|
# =============================================================================
|