TurfAITurfAI Developers
API Reference

Classification + RTIO API Documentation

Synced from the TurfAI source on 2026-06-21.

Overview

This document describes the Classification with RTIO (Role/Tasks/Instructions/Output) feature. The system uses a 2-phase approach to classify documents and either match them to existing extraction prompts or generate new ones.

RTIO Format: A structured prompt template consisting of:

  • Role: System and user role definitions for the LLM
  • Tasks: Specific extraction tasks to perform
  • Instructions: Clear guidelines for output formatting and validation
  • Output: Expected output format and example structure

Architecture:

  • DMS: Orchestrates classification and RTIO generation
  • Processors: Execute LLM operations (classification, semantic matching, RTIO generation)
  • UI: Handles user interaction, preview, and saving

Flow Diagram

User Uploads Document

[1] Classification Job (2-phase)
    Phase 1: Generic classification (document_type, category, key_features)
    Phase 2: Semantic matching against available prompts

Response:
  - If match found → matched_prompt_id + extraction data
  - If no match → rtio_available: true

[2] User clicks "Generate RTIO" (if no match)

RTIO Generation Job
    Analyzes document structure
    Generates extraction prompt

Response: rtio_prompt (JSON structure)

[3] UI Preview & Edit
    User reviews generated prompt
    Can edit title/description

[4] User clicks "Save"

POST /api/prompts (existing endpoint)
    Saves as user-level prompt

API Endpoints

1. Classification Job

Endpoint: POST /api/jobs/collection/:collection_id/generate

Purpose: Classify a document and find matching extraction prompt

Request:

{
  "job_type": "classification",
  "document_ids": [123],
  "metadata": {},
  "config": {}
}

Response (when match found):

{
  "job_id": "classify_1731234567890",
  "success": true,
  "message": "Classification job submitted"
}

Then poll: GET /api/jobs/classify_1731234567890/status

Result (match found):

{
  "status": "completed",
  "result": {
    "extraction_result": {
      "classification": "Offer Letter",
      "document_type": "Offer Letter",
      "category": "Employment",
      "key_features": ["company_name", "salary", "joining_date"],
      "confidence": 0.92,
      "reasoning": "Document contains standard offer letter...",
      "matched_prompt_id": 15,
      "match_confidence": 0.88,
      "match_reasoning": "Strong semantic match with Offer Letter extraction prompt"
    },
    "metadata": {
      "document_name": "offer_letter.pdf",
      "category": "Employment",
      "classification_method": "generic_classification_with_semantic_matching"
    }
  }
}

Result (no match found - RTIO available):

{
  "status": "completed",
  "result": {
    "extraction_result": {
      "classification": "Custom Employment Agreement",
      "document_type": "Custom Employment Agreement",
      "category": "Employment",
      "key_features": ["employer", "employee", "terms", "compensation"],
      "confidence": 0.85,
      "reasoning": "Document is a custom employment agreement...",
      "matched_prompt_id": null,
      "match_confidence": 0.0,
      "match_reasoning": ""
    },
    "metadata": {
      "document_name": "custom_agreement.pdf",
      "category": "Employment",
      "classification_method": "generic_classification_with_semantic_matching"
    }
  }
}

UI Logic:

if (result.extraction_result.matched_prompt_id) {
  // Match found - use the matched prompt for extraction
  const promptId = result.extraction_result.matched_prompt_id;
  // Proceed with extraction using this prompt
} else {
  // No match - show "Generate RTIO" button
  showGenerateRTIOButton(result.extraction_result);
}

2. Generate RTIO

Endpoint: POST /api/prompts/generate-rtio

Purpose: Generate a custom extraction prompt based on document analysis

Request:

{
  "document_id": 123,
  "classification_result": {
    "document_type": "Custom Employment Agreement",
    "category": "Employment",
    "key_features": ["employer", "employee", "terms", "compensation"],
    "confidence": 0.85
  }
}

Response:

{
  "success": true,
  "rtio_prompt": {
    "title": "Extract Custom Employment Agreement Details",
    "description": "Extracts key information from custom employment agreements",
    "section": "Employment",
    "roles": [
      {
        "role": "system",
        "prompt": "You are an employment document specialist trained to extract structured information..."
      },
      {
        "role": "user",
        "prompt": "From the provided employment agreement, extract the following fields:\n- Employer name\n- Employee name..."
      }
    ],
    "tasks": [
      "Extract employer and employee information",
      "Identify compensation structure and terms",
      "Extract contract duration and termination clauses"
    ],
    "instructions": [
      "Return output as valid JSON",
      "Use keys: employer, employee, compensation, start_date, etc.",
      "Mark unclear fields as null"
    ],
    "outputFormat": "json",
    "exampleOutput": {
      "employer": "Acme Corporation",
      "employee": "John Doe",
      "compensation": "$120,000 per year",
      "start_date": "2025-01-15",
      "duration": "Permanent",
      "benefits": ["Health insurance", "401k"]
    },
    "level": "user",
    "version": "1.0.0"
  },
  "confidence": 0.85,
  "generated_from_classification": {
    "document_type": "Custom Employment Agreement",
    "category": "Employment"
  },
  "message": "RTIO prompt generated successfully. You can now preview and save it."
}

Error Responses:

// 400 Bad Request - Missing parameters
{
  "error": {
    "status": 400,
    "message": "document_id and classification_result required"
  }
}

// 403 Forbidden - Not document owner
{
  "error": {
    "status": 403,
    "message": "You do not own this document"
  }
}

// 408 Request Timeout - Generation took too long
{
  "error": {
    "status": 408,
    "message": "RTIO generation timed out. Please try again with a smaller document."
  }
}

// 500 Internal Server Error - Generation failed
{
  "error": {
    "status": 500,
    "message": "Failed to generate RTIO",
    "details": {
      "error": "LLM request failed"
    }
  }
}

3. Save RTIO Prompt

Endpoint: POST /api/prompts (existing Strapi endpoint)

Purpose: Save the generated RTIO as a user-level prompt

Request:

{
  "data": {
    "title": "My Custom Employment Agreement Extractor",
    "description": "Custom prompt for extracting employment agreement details",
    "section": "Employment",
    "roles": [
      {
        "role": "system",
        "prompt": "..."
      },
      {
        "role": "user",
        "prompt": "..."
      }
    ],
    "tasks": [...],
    "instructions": [...],
    "outputFormat": "json",
    "exampleOutput": {...},
    "level": "user",
    "version": "1.0.0"
  }
}

Response:

{
  "id": 156,
  "title": "My Custom Employment Agreement Extractor",
  "description": "Custom prompt for extracting employment agreement details",
  "section": "Employment",
  "level": "user",
  "owner": {
    "id": 5,
    "username": "john_doe"
  },
  "createdAt": "2025-11-06T12:34:56.789Z",
  "updatedAt": "2025-11-06T12:34:56.789Z"
}

Notes:

  • level is automatically set to "user" for non-admin users
  • owner is automatically set to current user
  • Only Super Admin can create level: "system" prompts

UI Integration Guide

Complete Flow Implementation

// Step 1: Classify document
async function classifyDocument(documentId, collectionId) {
  const response = await fetch(`/api/jobs/collection/${collectionId}/generate`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${userToken}`
    },
    body: JSON.stringify({
      job_type: 'classification',
      document_ids: [documentId],
      metadata: {},
      config: {}
    })
  });

  const { job_id } = await response.json();

  // Poll for result
  return await pollJobStatus(job_id);
}

// Step 2: Check if RTIO is available
function handleClassificationResult(result) {
  const classification = result.extraction_result;

  if (classification.matched_prompt_id) {
    // Match found - proceed with extraction
    console.log('Matched prompt:', classification.matched_prompt_id);
    console.log('Match confidence:', classification.match_confidence);

    return {
      hasMatch: true,
      promptId: classification.matched_prompt_id,
      classification
    };
  } else {
    // No match - offer RTIO generation
    console.log('No match found. RTIO generation available.');

    return {
      hasMatch: false,
      rtioAvailable: true,
      classification
    };
  }
}

// Step 3: Generate RTIO (if no match)
async function generateRTIO(documentId, classificationResult) {
  const response = await fetch('/api/prompts/generate-rtio', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${userToken}`
    },
    body: JSON.stringify({
      document_id: documentId,
      classification_result: classificationResult
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error?.message || 'RTIO generation failed');
  }

  return await response.json();
}

// Step 4: Preview and edit RTIO
function showRTIOPreview(rtioData) {
  // Display in modal or panel
  const { rtio_prompt, confidence } = rtioData;

  // Allow user to edit title and description
  const editablePrompt = {
    ...rtio_prompt,
    title: userEditedTitle || rtio_prompt.title,
    description: userEditedDescription || rtio_prompt.description
  };

  return editablePrompt;
}

// Step 5: Save RTIO prompt
async function saveRTIOPrompt(rtioPrompt) {
  const response = await fetch('/api/prompts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${userToken}`
    },
    body: JSON.stringify({
      data: rtioPrompt
    })
  });

  if (!response.ok) {
    throw new Error('Failed to save RTIO prompt');
  }

  const savedPrompt = await response.json();
  console.log('✅ RTIO prompt saved:', savedPrompt.id);

  return savedPrompt;
}

// Complete workflow
async function classifyAndExtractWorkflow(documentId, collectionId) {
  try {
    // 1. Classify
    const classificationResult = await classifyDocument(documentId, collectionId);
    const analysis = handleClassificationResult(classificationResult.result);

    if (analysis.hasMatch) {
      // Use matched prompt for extraction
      console.log('Using matched prompt:', analysis.promptId);
      // Proceed with extraction...
    } else if (analysis.rtioAvailable) {
      // Show "Generate RTIO" button
      const userWantsRTIO = await showGenerateRTIODialog();

      if (userWantsRTIO) {
        // 2. Generate RTIO
        const rtioData = await generateRTIO(documentId, analysis.classification);

        // 3. Preview and allow editing
        const editedPrompt = await showRTIOPreview(rtioData);

        // 4. Save if user confirms
        const userConfirmed = await showSaveConfirmDialog();
        if (userConfirmed) {
          const savedPrompt = await saveRTIOPrompt(editedPrompt);

          // 5. Now use the saved prompt for extraction
          console.log('Using newly saved prompt:', savedPrompt.id);
          // Proceed with extraction...
        }
      }
    }
  } catch (error) {
    console.error('Workflow error:', error);
    showErrorMessage(error.message);
  }
}

Testing

Test Case 1: Known Document Type (Match Found)

Upload: Aadhaar Card document

Expected:

  1. Classification returns document_type: "Aadhaar Card"
  2. matched_prompt_id is set (system prompt for Aadhaar)
  3. match_confidence >= 0.7
  4. UI proceeds directly to extraction

Test Case 2: Unknown Document Type (RTIO Generation)

Upload: Custom contract document

Expected:

  1. Classification returns document_type: "Custom Contract"
  2. matched_prompt_id is null
  3. UI shows "Generate RTIO" button
  4. User clicks → RTIO generated
  5. User previews → edits title → saves
  6. Prompt saved as user-level
  7. Available for future extractions

Test Case 3: Error Handling

Scenarios:

  • Upload very large document → RTIO generation timeout (408)
  • Try to generate RTIO for someone else's document → Forbidden (403)
  • Missing classification_result → Bad Request (400)

Performance Expectations

  • Classification: 3-5 seconds (2 LLM calls)
  • RTIO Generation: 8-15 seconds (complex LLM generation)
  • Total Time (classify + RTIO): ~20 seconds maximum

Cost Considerations

  • Classification (per document): ~$0.02 (2 LLM calls with Vertex AI)
  • RTIO Generation (per prompt): ~$0.03-0.05 (longer generation)
  • Total (full classify + RTIO cycle): ~$0.05-0.07

Security

  • All endpoints require authentication (Authorization: Bearer <token>)
  • Users can only access their own documents
  • Users can only create user-level prompts (not system-level)
  • RTIO prompts automatically scoped to user

Future Enhancements

  1. RTIO Refinement: Allow users to regenerate RTIOs with feedback
  2. RTIO Sharing: Share user-generated prompts with team
  3. RTIO Templates: Provide starter templates for common document types
  4. Batch RTIO: Generate RTIOs for multiple documents at once
  5. RTIO Analytics: Track which RTIOs are most used/accurate

Support

For questions or issues:

  • Backend API: Check DMS logs (dms/logs/)
  • Processor issues: Check processor logs (processors/logs/)
  • Router issues: Check router logs (router/logs/)

Key Log Messages:

  • ✅ Prepared semantic matching metadata - Classification setup successful
  • 🎯 Generating RTIO for document - RTIO generation started
  • ✅ RTIO generation completed successfully - RTIO ready
  • ❌ RTIO generation failed - Check error details

On this page