Files
Alejandro Gutiérrez 3eda9bdbfa Add complete URT v5.1 taxonomy framework (11 artifacts)
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>
2026-01-24 10:51:41 +00:00

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
# =============================================================================