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