# API Keys Source: https://docs.hitl.sh/api-keys Generate and manage API keys to securely integrate HITL.sh with your applications and workflows # API Keys API keys are your secure credentials for integrating HITL.sh with your applications, workflows, and third-party tools. They authenticate your requests and ensure only authorized systems can interact with your loops and data. ## Understanding API Keys API keys in HITL.sh serve as your application's identity and provide secure access to: * **Loop Management**: Create, read, update, and delete loops * **Request Operations**: Submit requests and retrieve responses * **Loop Members**: Add and remove reviewers from loops * **Webhook Configuration**: Set up event notifications * **Request Feedback**: Add feedback to completed requests Keep your API keys secure and never expose them in client-side code or public repositories. Treat them like passwords. ## Generating Your First API Key Visit [my.hitl.sh](https://my.hitl.sh) and log in to your account. Go to Settings → API Keys from the main navigation menu. Click the "Create New API Key" button to generate a new key. Copy the generated API key immediately and store it securely in environment variables. The key is only shown once for security reasons. API keys are shown only once when created. Store them immediately in a secure location like environment variables or a secrets manager. ## API Key Management ### Viewing Active Keys Your dashboard shows all active API keys with their: * **Name**: Descriptive label for easy identification * **Permissions**: Access level granted to the key * **Created Date**: When the key was generated * **Last Used**: Most recent activity timestamp * **Status**: Active, suspended, or expired ### API Key Permissions API keys have specific permissions based on your account and plan: * Create, read, update, and delete loops * Manage loop members (add/remove reviewers) * View loop statistics and activity * Create requests in loops you own * View and cancel your requests * Add feedback to completed requests * Access request history and responses * Set up webhook endpoints for real-time notifications * Configure webhook events and filters * View webhook delivery logs and status ### Security Best Practices Store API keys in environment variables, never hardcode them in your source code. Regularly rotate your API keys to minimize the impact of potential compromises. Grant only the minimum permissions necessary for each integration. Regularly review API key usage to detect unauthorized access. ## Using API Keys ### Authentication Header Include your API key in the `Authorization` header of all API requests: ```bash theme={null} Authorization: Bearer YOUR_API_KEY_HERE ``` ### Testing Your API Key Use the test endpoint to verify your API key is working correctly: ```bash cURL theme={null} curl -X GET 'https://api.hitl.sh/v1/test' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python theme={null} import requests headers = { 'Authorization': 'Bearer YOUR_API_KEY' } response = requests.get('https://api.hitl.sh/v1/test', headers=headers) data = response.json() if data["error"] == False: print("✅ API key is valid!") print(f"Rate limit: {data['data']['rate_limit']['remaining']}/{data['data']['rate_limit']['limit']}") else: print("❌ API key validation failed") ``` ```javascript Node.js theme={null} const axios = require('axios'); const headers = { 'Authorization': 'Bearer YOUR_API_KEY' }; async function testApiKey() { try { const response = await axios.get('https://api.hitl.sh/v1/test', { headers }); if (!response.data.error) { console.log('✅ API key is valid!'); console.log(`Rate limit: ${response.data.data.rate_limit.remaining}/${response.data.data.rate_limit.limit}`); } } catch (error) { console.error('❌ API key validation failed:', error.response?.data); } } testApiKey(); ``` **Expected Response:** ```json theme={null} { "error": false, "msg": "API key is valid", "data": { "api_key_id": "65f1234567890abcdef12349", "user_id": "65f1234567890abcdef12346", "email": "user@example.com", "account_status": "active", "rate_limit": { "limit": 100, "remaining": 95, "reset_at": "2024-03-15T15:00:00Z" }, "permissions": ["loops:read", "loops:write", "requests:read", "requests:write"] } } ``` ## Rate Limits API keys have usage limits to ensure fair usage: * **100 requests per hour** per API key * Rate limits reset at the **top of each hour** (e.g., 1:00 PM, 2:00 PM, not rolling 60 minutes) * Rate limit information included in response headers * `X-RateLimit-Reset` header shows the exact UTC timestamp when the limit resets **Rate Limit Headers:** ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 85 X-RateLimit-Reset: 1642237200 ``` ### Handling Rate Limits ```python theme={null} import time import requests def make_api_request_with_retry(url, headers, data=None, max_retries=3): for attempt in range(max_retries): try: if data: response = requests.post(url, headers=headers, json=data) else: response = requests.get(url, headers=headers) if response.status_code == 429: # Rate limited - wait and retry retry_after = int(response.headers.get('Retry-After', 60)) print(f"Rate limited. Waiting {retry_after} seconds...") time.sleep(retry_after) continue return response except Exception as e: if attempt == max_retries - 1: raise e time.sleep(2 ** attempt) # Exponential backoff return None ``` ## Integration Examples ### Creating Your First Loop ```python theme={null} import requests api_key = "YOUR_API_KEY" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } # Create a new loop response = requests.post( "https://api.hitl.sh/v1/api/loops", headers=headers, json={ "name": "Content Moderation Loop", "description": "Review user-generated content for compliance" } ) if response.status_code == 200: loop_data = response.json() print(f"✅ Loop created: {loop_data['data']['id']}") ``` ### Submitting a Request ```javascript theme={null} const axios = require('axios'); const apiKey = 'YOUR_API_KEY'; const loopId = 'your_loop_id'; const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; async function submitRequest() { try { const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${loopId}/requests`, { processing_type: 'time-sensitive', type: 'markdown', priority: 'medium', request_text: 'Please review this content for appropriateness.', response_type: 'single_select', response_config: { options: [ { value: 'approve', label: 'Approve' }, { value: 'reject', label: 'Reject' } ], required: true }, timeout_seconds: 3600, platform: 'api' }, { headers } ); console.log('✅ Request submitted:', response.data.data.id); return response.data.data; } catch (error) { console.error('❌ Error submitting request:', error.response?.data); } } submitRequest(); ``` ## Troubleshooting ### Common Issues **Symptoms:** `{"error": true, "msg": "Invalid authorization token"}` **Solutions:** * Verify the API key is copied correctly without extra spaces * Check if the key has been revoked in your dashboard * Ensure you're using the Bearer format: `Authorization: Bearer YOUR_API_KEY` * Confirm you're not including extra characters or line breaks **Symptoms:** `{"error": true, "msg": "Access denied to this resource"}` **Solutions:** * Verify your API key has the required permissions * Check if you're trying to access resources you don't own * Ensure your account has the necessary features enabled **Symptoms:** `{"error": true, "msg": "API rate limit exceeded"}` **Solutions:** * Wait for the rate limit to reset (check `X-RateLimit-Reset` header) * Implement exponential backoff in your retry logic * Use the `/test` endpoint to check your current rate limit status * Consider batching requests to optimize usage **Symptoms:** Connection timeouts or network errors **Solutions:** * Verify you can reach `api.hitl.sh` from your network * Check if you're behind a corporate firewall blocking the API * Ensure you're using HTTPS (not HTTP) for all requests * Try from a different network to isolate connectivity issues ### Debug Authentication Use this debug script to troubleshoot API key issues: ```python theme={null} import requests def debug_api_key(api_key): """Debug API key authentication issues""" headers = {"Authorization": f"Bearer {api_key}"} try: response = requests.get("https://api.hitl.sh/v1/test", headers=headers) print(f"Status Code: {response.status_code}") print(f"Response: {response.json()}") if response.status_code == 200: data = response.json()["data"] print("\n🔍 API Key Debug Info:") print(f" Account: {data['email']}") print(f" Status: {data['account_status']}") print(f" Rate Limit: {data['rate_limit']['remaining']}/{data['rate_limit']['limit']}") print(f" Permissions: {', '.join(data['permissions'])}") except Exception as e: print(f"❌ Error: {e}") # Test your API key debug_api_key("YOUR_API_KEY_HERE") ``` ## Next Steps Now that you have your API key set up, you're ready to: Verify your API key is working with the test endpoint. Set up a human-in-the-loop workflow using the API. Send content for human review and get responses. Explore all available endpoints and integration options. # Authentication Source: https://docs.hitl.sh/api-reference/authentication Learn about API key authentication and security best practices for accessing the HITL.sh API # Authentication HITL.sh uses API key authentication for secure access to the API. All requests must include your API key in the Authorization header using the Bearer token format. ## API Key Authentication API keys are designed for server-to-server communication and automated workflows. They provide access to the HITL.sh API at `https://api.hitl.sh/v1`. ### Getting Your API Key 1. **Log in** to your HITL.sh dashboard 2. **Navigate** to Settings → API Keys 3. **Click** "Create New API Key" 4. **Copy** the generated key (shown only once) 5. **Store securely** in your environment variables ### Using API Keys Include your API key in the `Authorization` header with the `Bearer` prefix: **Using the API Playground**: When testing endpoints in the documentation playground, enter your API key in the format: `Bearer your_api_key_here` (including the word "Bearer" and a space before your key). ```bash theme={null} curl -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ https://api.hitl.sh/v1/api/loops ``` ```python Python theme={null} import requests headers = { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json' } response = requests.get('https://api.hitl.sh/v1/api/loops', headers=headers) ``` ```javascript Node.js theme={null} const axios = require('axios'); const headers = { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json' }; const response = await axios.get('https://api.hitl.sh/v1/api/loops', { headers }); ``` ```go Go theme={null} package main import ( "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://api.hitl.sh/v1/api/loops", nil) req.Header.Set("Authorization", "Bearer your_api_key_here") req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(req) } ``` ```php PHP theme={null} "https://api.hitl.sh/v1/api/loops", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer your_api_key_here", "Content-Type: application/json" ], ]); $response = curl_exec($curl); curl_close($curl); ?> ``` ### API Key Rate Limits Each API key has the following limits: * **100 requests per hour** per API key * Resets at the **top of each hour** (e.g., 1:00 PM, 2:00 PM) * Rate limit headers included in all responses * `X-RateLimit-Reset` shows exact UTC timestamp of next reset **Rate limit headers:** ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 85 X-RateLimit-Reset: 1642237200 ``` ### API Key Permissions API keys have specific permissions based on your account: * Create new loops * View loops you created or are a member of * Update and delete loops you created (creator only) * Add/remove members from loops you created (creator only) * View loop statistics and member lists * Create requests in loops you created * View requests in loops you're a member of * Cancel your own requests * Add feedback to completed requests you created * Access request history and response data ## Security Best Practices ### API Key Security Store API keys in environment variables, never in code: ```bash theme={null} export HITL_API_KEY="your_api_key_here" ``` ```python theme={null} import os api_key = os.environ.get('HITL_API_KEY') ``` Rotate API keys regularly: * Set up automatic rotation (recommended: every 90 days) * Have a backup key ready before rotating * Update all systems using the old key Use separate API keys for different environments: * Development keys with limited permissions * Production keys with full access * Testing keys for CI/CD pipelines Track API key usage in your dashboard: * Monitor request patterns for anomalies * Set up alerts for unusual activity * Review access logs regularly ### Secure Headers Always use HTTPS and proper security headers: ```python theme={null} import requests headers = { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json', 'User-Agent': 'YourApp/1.0.0', 'Accept': 'application/json' } # Always use HTTPS response = requests.get('https://api.hitl.sh/v1/api/loops', headers=headers) ``` ## Error Handling ### Authentication Errors ```json theme={null} { "error": true, "msg": "Missing Authorization header" } ``` **Solutions:** * Include the Authorization header in your request * Use the correct format: `Authorization: Bearer your_api_key_here` ```json theme={null} { "error": true, "msg": "Invalid Authorization header format. Use 'Bearer '" } ``` **Solutions:** * Ensure header starts with "Bearer " (with a space after it) * Check for typos in the header format * Don't use quotes around the API key value ```json theme={null} { "error": true, "msg": "Invalid API key" } ``` **Solutions:** * Verify API key is correct (no typos or extra spaces) * Check if key has been revoked or deleted * Regenerate a new API key if needed * Ensure you're not using an old/expired key ```json theme={null} { "error": true, "msg": "API key is inactive" } ``` **Solutions:** * Check API key status in your dashboard * Reactivate the key if it was disabled * Generate a new API key if needed ```json theme={null} { "error": true, "msg": "Access denied to this resource" } ``` **Solutions:** * Check API key permissions * Verify resource ownership * Contact support for permission updates ```json theme={null} { "error": true, "msg": "API rate limit exceeded", "data": { "usage_count": 100, "usage_limit": 100, "remaining": 0 } } ``` **Solutions:** * Wait for rate limit reset * Implement exponential backoff * Upgrade to higher tier if needed ### Retry Logic Implement robust retry logic for authentication failures: ```python Python theme={null} import time import requests from functools import wraps def retry_auth(max_retries=3): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: response = func(*args, **kwargs) if response.status_code == 401: # Log the authentication failure print(f"Authentication failed on attempt {attempt + 1}") if attempt < max_retries - 1: # Wait before retrying (maybe refresh API key) time.sleep(2 ** attempt) continue return response except Exception as e: if attempt == max_retries - 1: raise e time.sleep(2 ** attempt) # Exponential backoff return None return wrapper return decorator @retry_auth() def make_api_request(url, headers): return requests.get(url, headers=headers) ``` ```javascript Node.js theme={null} async function makeRequestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url, options); if (response.status === 401) { console.log(`Authentication failed on attempt ${attempt + 1}`); if (attempt < maxRetries - 1) { // Wait before retrying await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000) ); continue; } } return response; } catch (error) { if (attempt === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000) ); } } } ``` ## Testing Authentication ### API Key Testing Use the dedicated test endpoint to verify your API key: ```bash theme={null} # Test API key validity and get account info curl -H "Authorization: Bearer your_api_key_here" \ https://api.hitl.sh/v1/test # Expected response for valid key: { "error": false, "msg": "API key is valid", "data": { "api_key_id": "65f1234567890abcdef12349", "user_id": "65f1234567890abcdef12346", "email": "user@example.com", "account_status": "active", "rate_limit": { "limit": 100, "remaining": 95, "reset_at": "2024-03-15T15:00:00Z" }, "permissions": ["loops:read", "loops:write", "requests:read", "requests:write"] } } # Expected response for invalid key: { "error": true, "msg": "Invalid API key" } ``` The `/test` endpoint provides detailed information about your API key, including rate limits and permissions. Use this for debugging and monitoring. ### Debug Authentication Issues Use verbose curl to debug authentication problems: ```bash theme={null} # Verbose request to see headers and response curl -v -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ https://api.hitl.sh/v1/api/loops # Check if your header is being sent correctly curl -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -w "HTTP Status: %{http_code}\n" \ https://api.hitl.sh/v1/api/loops ``` ### Environment-Specific Testing Test across different environments: ```python theme={null} import os import requests # Load environment-specific API key def get_api_key(): env = os.environ.get('ENVIRONMENT', 'development') key_mapping = { 'development': os.environ.get('HITL_DEV_API_KEY'), 'staging': os.environ.get('HITL_STAGING_API_KEY'), 'production': os.environ.get('HITL_PROD_API_KEY') } return key_mapping.get(env) def test_authentication(): api_key = get_api_key() if not api_key: raise ValueError("API key not found for current environment") headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' } response = requests.get('https://api.hitl.sh/v1/api/loops', headers=headers) if response.status_code == 200: print("✅ Authentication successful") return True else: print(f"❌ Authentication failed: {response.json()}") return False # Test authentication test_authentication() ``` ## Next Steps Start using your API key to create loops and manage workflows. Learn how to handle and debug authentication errors effectively. Use callback URLs to receive notifications when requests complete. # Error Handling Source: https://docs.hitl.sh/api-reference/errors Complete reference for HITL.sh API errors, status codes, and troubleshooting guide # Error Handling HITL.sh APIs use conventional HTTP status codes and provide detailed error messages to help you debug issues quickly. All errors follow a consistent format for easy parsing and handling. ## Error Response Format All API errors return a JSON response with the following structure: ```json theme={null} { "error": true, "msg": "Human-readable error description", "data": { // Additional error details (optional) } } ``` ### Error Response Fields Always `true` for error responses Human-readable error message describing what went wrong Additional error context, validation details, or debugging information (optional) ## HTTP Status Codes ### 2xx Success Request succeeded. Response contains the requested data. ```json theme={null} { "error": false, "msg": "Operation completed successfully", "data": { /* response data */ } } ``` Resource was created successfully. ```json theme={null} { "error": false, "msg": "Loop created successfully", "data": { /* created resource */ } } ``` ### 4xx Client Errors Request data is invalid or malformed. **Common causes:** * Missing required fields * Invalid field values * Malformed JSON * Validation failures **Invalid request body:** ```json theme={null} { "error": true, "msg": "Invalid request body" } ``` **Invalid ID format:** ```json theme={null} { "error": true, "msg": "Invalid loop ID format" } ``` ```json theme={null} { "error": true, "msg": "Invalid request ID format" } ``` **Missing required fields:** ```json theme={null} { "error": true, "msg": "Loop name is required" } ``` ```json theme={null} { "error": true, "msg": "Loop icon is required" } ``` ```json theme={null} { "error": true, "msg": "Request text is required" } ``` ```json theme={null} { "error": true, "msg": "timeout_seconds is required for time-sensitive requests" } ``` ```json theme={null} { "error": true, "msg": "Image URL is required when type is image" } ``` **Invalid configuration:** ```json theme={null} { "error": true, "msg": "Invalid response configuration", "data": "options array required for select response type" } ``` ```json theme={null} { "error": true, "msg": "Invalid default response", "data": "default_response must match one of the configured options" } ``` **State validation errors:** ```json theme={null} { "error": true, "msg": "No active members found in the loop" } ``` ```json theme={null} { "error": true, "msg": "Request cannot be cancelled in current state" } ``` ```json theme={null} { "error": true, "msg": "Feedback can only be added to completed requests" } ``` Authentication is missing or invalid. **Common causes:** * Missing API key * Invalid API key * Malformed authorization header * Inactive API key **Missing Authorization header:** ```json theme={null} { "error": true, "msg": "Missing Authorization header" } ``` **Invalid header format:** ```json theme={null} { "error": true, "msg": "Invalid Authorization header format. Use 'Bearer '" } ``` **Empty API key:** ```json theme={null} { "error": true, "msg": "Empty API key" } ``` **Invalid API key:** ```json theme={null} { "error": true, "msg": "Invalid API key" } ``` **Inactive API key:** ```json theme={null} { "error": true, "msg": "API key is inactive" } ``` **API key required:** ```json theme={null} { "error": true, "msg": "API key required" } ``` Authentication is valid but access is denied. **Common causes:** * Not the resource owner (loop creator) * Not a member of the loop * Insufficient permissions for the operation **Access denied to loop:** ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` **Access denied to request:** ```json theme={null} { "error": true, "msg": "Access denied to this request" } ``` **Creator-only operations:** Only loop creators can create requests: ```json theme={null} { "error": true, "msg": "Only loop creators can create requests" } ``` Only loop creator can update: ```json theme={null} { "error": true, "msg": "Only loop creator can update the loop" } ``` Only loop creator can delete: ```json theme={null} { "error": true, "msg": "Only loop creator can delete the loop" } ``` Only loop creator can remove members: ```json theme={null} { "error": true, "msg": "Only loop creator can remove members" } ``` Loop creator cannot remove themselves: ```json theme={null} { "error": true, "msg": "Loop creator cannot remove themselves" } ``` The requested resource doesn't exist. **Common causes:** * Invalid resource ID * Resource was deleted * Typo in endpoint URL * Resource doesn't belong to your account **Loop not found:** ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Request not found:** ```json theme={null} { "error": true, "msg": "Request not found" } ``` **User is not a member:** ```json theme={null} { "error": true, "msg": "User is not a member of this loop" } ``` Rate limit has been exceeded. Limits reset at the top of each hour. **Rate limit exceeded:** ```json theme={null} { "error": true, "msg": "Rate limit exceeded. Maximum 100 requests per hour per API key." } ``` **API key usage limit exceeded:** ```json theme={null} { "error": true, "msg": "API key usage limit exceeded" } ``` **Response headers (included in all responses):** ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1704204000 ``` The `X-RateLimit-Reset` header contains a Unix timestamp indicating when the rate limit will reset (top of the next hour). ### 5xx Server Errors An unexpected error occurred on our servers. **Common server errors:** Database errors: ```json theme={null} { "error": true, "msg": "Failed to retrieve loops" } ``` ```json theme={null} { "error": true, "msg": "Failed to retrieve loop" } ``` ```json theme={null} { "error": true, "msg": "Failed to create loop" } ``` ```json theme={null} { "error": true, "msg": "Failed to update loop" } ``` ```json theme={null} { "error": true, "msg": "Failed to retrieve requests" } ``` ```json theme={null} { "error": true, "msg": "Failed to retrieve request" } ``` ```json theme={null} { "error": true, "msg": "Failed to cancel request" } ``` ```json theme={null} { "error": true, "msg": "Failed to add feedback" } ``` User information errors: ```json theme={null} { "error": true, "msg": "Failed to get user information" } ``` **What to do:** * Retry the request after a short delay * Check our status page at [status.hitl.sh](https://status.hitl.sh) * Contact support if the issue persists Gateway or proxy error, usually temporary. ```json theme={null} { "error": true, "msg": "Service temporarily unavailable" } ``` Service is temporarily unavailable, usually due to maintenance. ```json theme={null} { "error": true, "msg": "Service temporarily unavailable" } ``` ## Common Error Scenarios ### Validation Errors **Request Text Too Long:** ```json theme={null} { "error": true, "msg": "Validation failed", "data": "request_text must be between 1 and 2000 characters" } ``` **Invalid Response Configuration:** ```json theme={null} { "error": true, "msg": "Invalid response configuration", "data": "options array required for select response type" } ``` **Invalid Enum Value:** ```json theme={null} { "error": true, "msg": "Validation failed", "data": "priority must be one of: low, medium, high, critical" } ``` ### Resource Access Errors **Loop Not Found:** ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Request Already Cancelled:** ```json theme={null} { "error": true, "msg": "Request cannot be cancelled in current state" } ``` **No Active Members:** ```json theme={null} { "error": true, "msg": "No active members found in the loop" } ``` ### Authentication Errors **Missing API Key:** ```json theme={null} { "error": true, "msg": "API key required" } ``` **Invalid API Key Format:** ```json theme={null} { "error": true, "msg": "Invalid API key format" } ``` **Expired Session:** ```json theme={null} { "error": true, "msg": "Session has expired. Please log in again." } ``` ## Error Handling Best Practices ### 1. Implement Retry Logic Use exponential backoff for transient errors: ```python Python theme={null} import time import random import requests def make_request_with_retry(url, headers, data=None, max_retries=3): retryable_status_codes = [429, 500, 502, 503, 504] for attempt in range(max_retries): try: if data: response = requests.post(url, headers=headers, json=data) else: response = requests.get(url, headers=headers) if response.status_code not in retryable_status_codes: return response if attempt < max_retries - 1: # Don't delay on last attempt # Exponential backoff with jitter delay = (2 ** attempt) + random.uniform(0, 1) time.sleep(delay) except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise e time.sleep(2 ** attempt) return response ``` ```javascript Node.js theme={null} async function makeRequestWithRetry(url, options, maxRetries = 3) { const retryableStatusCodes = [429, 500, 502, 503, 504]; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url, options); if (!retryableStatusCodes.includes(response.status)) { return response; } if (attempt < maxRetries - 1) { // Exponential backoff with jitter const delay = (2 ** attempt) * 1000 + Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } catch (error) { if (attempt === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000)); } } } ``` ### 2. Handle Rate Limits Gracefully ```python Python theme={null} import time import requests class HITLAPIClient: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://api.hitl.sh/v1" def make_request(self, method, endpoint, data=None): url = f"{self.base_url}{endpoint}" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } response = requests.request(method, url, headers=headers, json=data) if response.status_code == 429: # Extract reset time from response reset_time = response.headers.get('X-RateLimit-Reset') if reset_time: wait_time = int(reset_time) - int(time.time()) if wait_time > 0: print(f"Rate limited. Waiting {wait_time} seconds...") time.sleep(wait_time) # Retry the request return self.make_request(method, endpoint, data) return response ``` ```javascript Node.js theme={null} class HITLAPIClient { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://api.hitl.sh/v1'; } async makeRequest(method, endpoint, data = null) { const url = `${this.baseUrl}${endpoint}`; const options = { method, headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }; if (data) { options.body = JSON.stringify(data); } const response = await fetch(url, options); if (response.status === 429) { const resetTime = response.headers.get('X-RateLimit-Reset'); if (resetTime) { const waitTime = parseInt(resetTime) - Math.floor(Date.now() / 1000); if (waitTime > 0) { console.log(`Rate limited. Waiting ${waitTime} seconds...`); await new Promise(resolve => setTimeout(resolve, waitTime * 1000)); return this.makeRequest(method, endpoint, data); } } } return response; } } ``` ### 3. Validate Requests Client-Side Implement client-side validation to catch errors early: ```python Python theme={null} def validate_create_request_payload(payload): """Validate request payload before sending to API""" errors = [] # Required fields required_fields = ['processing_type', 'type', 'priority', 'request_text', 'response_type', 'response_config', 'default_response', 'platform'] for field in required_fields: if field not in payload or not payload[field]: errors.append(f"{field} is required") # Enum validations if 'processing_type' in payload: valid_processing_types = ['time-sensitive', 'deferred'] if payload['processing_type'] not in valid_processing_types: errors.append(f"processing_type must be one of: {', '.join(valid_processing_types)}") if 'priority' in payload: valid_priorities = ['low', 'medium', 'high', 'critical'] if payload['priority'] not in valid_priorities: errors.append(f"priority must be one of: {', '.join(valid_priorities)}") # Custom validations if payload.get('processing_type') == 'time-sensitive' and 'timeout_seconds' not in payload: errors.append("timeout_seconds is required for time-sensitive requests") if 'timeout_seconds' in payload: timeout = payload['timeout_seconds'] if not isinstance(timeout, int) or timeout < 60 or timeout > 86400: errors.append("timeout_seconds must be between 60 and 86400") return errors ``` ```javascript Node.js theme={null} function validateCreateRequestPayload(payload) { const errors = []; // Required fields const requiredFields = ['processing_type', 'type', 'priority', 'request_text', 'response_type', 'response_config', 'default_response', 'platform']; for (const field of requiredFields) { if (!payload[field]) { errors.push(`${field} is required`); } } // Enum validations if (payload.processing_type) { const validProcessingTypes = ['time-sensitive', 'deferred']; if (!validProcessingTypes.includes(payload.processing_type)) { errors.push(`processing_type must be one of: ${validProcessingTypes.join(', ')}`); } } if (payload.priority) { const validPriorities = ['low', 'medium', 'high', 'critical']; if (!validPriorities.includes(payload.priority)) { errors.push(`priority must be one of: ${validPriorities.join(', ')}`); } } // Custom validations if (payload.processing_type === 'time-sensitive' && !payload.timeout_seconds) { errors.push('timeout_seconds is required for time-sensitive requests'); } if (payload.timeout_seconds) { const timeout = payload.timeout_seconds; if (!Number.isInteger(timeout) || timeout < 60 || timeout > 86400) { errors.push('timeout_seconds must be between 60 and 86400'); } } return errors; } ``` ### 4. Log Errors for Debugging Implement comprehensive error logging: ```python Python theme={null} import logging import json logger = logging.getLogger(__name__) def log_api_error(response, endpoint, payload=None): """Log API errors with context for debugging""" try: error_data = response.json() except: error_data = {"msg": "Failed to parse error response"} log_entry = { "endpoint": endpoint, "status_code": response.status_code, "error_message": error_data.get("msg", "Unknown error"), "error_data": error_data.get("data"), "request_payload": payload, "response_headers": dict(response.headers) } logger.error(f"API Error: {json.dumps(log_entry, indent=2)}") return log_entry # Usage response = make_api_request(url, payload) if response.status_code >= 400: log_api_error(response, endpoint, payload) ``` ```javascript Node.js theme={null} function logAPIError(response, endpoint, payload = null) { const logEntry = { endpoint, status: response.status, statusText: response.statusText, requestPayload: payload, timestamp: new Date().toISOString() }; response.json().then(errorData => { logEntry.errorMessage = errorData.msg || 'Unknown error'; logEntry.errorData = errorData.data; console.error('API Error:', JSON.stringify(logEntry, null, 2)); // Send to your logging service // sendToLoggingService(logEntry); }).catch(() => { logEntry.errorMessage = 'Failed to parse error response'; console.error('API Error:', JSON.stringify(logEntry, null, 2)); }); return logEntry; } ``` ## Debugging Guide ### Common Issues and Solutions **Symptoms:** Getting 401 Unauthorized errors **Debugging steps:** 1. Verify API key is correct (no extra spaces) 2. Check header format: `Authorization: Bearer your_key_here` 3. Ensure key hasn't been revoked in dashboard 4. Try generating a new API key ```bash theme={null} # Test API key curl -v -H "Authorization: Bearer your_api_key" https://api.hitl.sh/v1/api/loops ``` **Symptoms:** Getting 400 Bad Request with validation errors **Debugging steps:** 1. Check required fields are present 2. Verify enum values are correct 3. Validate field types and formats 4. Check field length constraints ```python theme={null} # Debug validation import json payload = { "name": "Test Loop", "icon": "test" } print("Payload:", json.dumps(payload, indent=2)) # Check against API requirements required_fields = ["name", "icon"] for field in required_fields: if field not in payload: print(f"Missing required field: {field}") ``` **Symptoms:** Getting 429 Too Many Requests **Debugging steps:** 1. Check rate limit headers in response 2. Implement exponential backoff 3. Consider upgrading API tier 4. Cache responses where possible ```bash theme={null} # Check rate limit status curl -I -H "Authorization: Bearer your_api_key" https://api.hitl.sh/v1/api/loops # Look for these headers: # X-RateLimit-Limit: 100 # X-RateLimit-Remaining: 0 # X-RateLimit-Reset: 1642237200 ``` **Symptoms:** Requests timing out or 504 errors **Debugging steps:** 1. Check our status page: [status.hitl.sh](https://status.hitl.sh) 2. Increase request timeout in your client 3. Try the request again after a delay 4. Contact support if issue persists ```python theme={null} # Increase timeout import requests response = requests.get( url, headers=headers, timeout=30 # 30 second timeout ) ``` ### Request/Response Debugging Enable verbose logging to see full HTTP requests and responses: ```bash cURL theme={null} # Use -v flag for verbose output curl -v -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{"name": "Test Loop", "icon": "test"}' \ https://api.hitl.sh/v1/api/loops ``` ```python Python theme={null} import requests import logging # Enable debug logging logging.basicConfig(level=logging.DEBUG) logging.getLogger("requests.packages.urllib3").setLevel(logging.DEBUG) logging.getLogger("urllib3.connectionpool").setLevel(logging.DEBUG) response = requests.post(url, headers=headers, json=data) ``` ```javascript Node.js theme={null} // Use a library like axios-debug-log const axios = require('axios'); // Create axios instance with logging const api = axios.create({ baseURL: 'https://api.hitl.sh/v1', headers: { 'Authorization': 'Bearer your_api_key' } }); // Log requests and responses api.interceptors.request.use(request => { console.log('Request:', request); return request; }); api.interceptors.response.use( response => { console.log('Response:', response); return response; }, error => { console.log('Error:', error.response); return Promise.reject(error); } ); ``` ## Getting Help ### Self-Service Resources Check if there are any ongoing service issues. Ask questions and get help from the community. Review API documentation and examples. Report bugs or request new features. ### Contacting Support When contacting support, please include: 1. **Request ID** (if available from response headers) 2. **Timestamp** of when the error occurred 3. **Full error response** including status code and message 4. **Request details** (endpoint, method, payload) 5. **Your API key ID** (not the actual key) Email us at [support@hitl.sh](mailto:support@hitl.sh) with your issue details. ## Next Steps Learn about API keys and security best practices. Use callback URLs to receive notifications when requests complete. # API Reference Source: https://docs.hitl.sh/api-reference/introduction Complete API documentation for HITL.sh. Integrate human oversight into your AI workflows with our RESTful API. ## HITL.sh API Reference The HITL.sh API provides a RESTful interface for integrating human supervision into your AI workflows. Create loops, manage requests, and route decisions to human reviewers with our comprehensive API. ## Base URL ``` https://api.hitl.sh/v1 ``` ## Authentication Use API keys for machine-to-machine integration. Include your API key in the header: ```bash theme={null} Authorization: Bearer your_api_key_here ``` **Rate Limits:** * **100 requests per hour** per API key * Rate limit headers included in responses: ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 99 X-RateLimit-Reset: 1642233600 ``` ## Response Format All API responses follow a consistent JSON format: **Success Response:** ```json theme={null} { "error": false, "msg": "Operation completed successfully", "data": { // Response data } } ``` **Error Response:** ```json theme={null} { "error": true, "msg": "Error description", "data": { // Additional error details (optional) } } ``` ## HTTP Status Codes * **200 OK**: Request successful * **201 Created**: Resource created successfully * **400 Bad Request**: Invalid request data or validation error * **401 Unauthorized**: Invalid or missing authentication * **403 Forbidden**: Access denied to resource * **404 Not Found**: Resource not found * **429 Too Many Requests**: Rate limit exceeded * **500 Internal Server Error**: Server error ## Core Resources ### Loops Loops are human review workflows that route requests to team members: * **POST** `/api/loops` - Create a new loop * **GET** `/api/loops` - Get your loops * **GET** `/api/loops/{id}` - Get specific loop * **PUT** `/api/loops/{id}` - Update loop * **DELETE** `/api/loops/{id}` - Delete loop * **GET** `/api/loops/{id}/members` - Get loop members * **DELETE** `/api/loops/{id}/members/{userId}` - Remove member ### Requests Requests are human review tasks sent to loop members: * **POST** `/api/loops/{loopId}/requests` - Create request in loop * **GET** `/api/requests` - Get your requests * **GET** `/api/requests/{id}` - Get specific request * **DELETE** `/api/requests/{id}` - Cancel request * **POST** `/api/requests/{id}/feedback` - Add feedback * **GET** `/api/loops/{loopId}/requests` - Get loop requests ## Quick Start Example ### 1. Create a Loop ```bash theme={null} curl -X POST https://api.hitl.sh/v1/api/loops \ -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Content Moderation", "description": "Review user-generated content", "icon": "shield-check" }' ``` ### 2. Create a Request ```bash theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/LOOP_ID/requests \ -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user comment for community guideline compliance", "response_type": "single_select", "response_config": { "options": [ { "value": "approve", "label": "✅ Approve Content" }, { "value": "reject", "label": "❌ Reject Content" }, { "value": "escalate", "label": "🚨 Escalate for Review" } ], "required": true }, "default_response": "reject", "timeout_seconds": 1800, "platform": "api" }' ``` ### 3. Poll for Response ```bash theme={null} curl -X GET https://api.hitl.sh/v1/api/requests/REQUEST_ID \ -H "Authorization: Bearer your_api_key_here" ``` ## Request Types and Response Configuration ### Response Types User chooses one option from a list. User chooses multiple options from a list. User provides a numerical rating within a range. User provides free-form text response. User provides a numerical value. ### Processing Types Requires immediate attention with specified timeout. `timeout_seconds` required. Can be processed within a longer timeframe (default: 30 days). ## Webhook Integration Set up webhooks to receive real-time notifications when requests are completed: ```json theme={null} { "event": "request.completed", "request_id": "65f1234567890abcdef12348", "status": "completed", "response_data": "Approve", "response_by": "reviewer@example.com", "completed_at": "2024-03-15T10:45:00Z" } ``` ## Error Handling ### Common Error Scenarios **Rate limit exceeded:** ```json theme={null} { "error": true, "msg": "API key request limit exceeded", "data": { "usage_count": 100, "usage_limit": 100, "remaining": 0 } } ``` **Validation error:** ```json theme={null} { "error": true, "msg": "Validation failed", "data": "timeout_seconds is required for time-sensitive requests" } ``` **Access denied:** ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` ## Best Practices ### API Usage * **Use HTTPS**: Always use secure connections * **Store keys securely**: Never commit API keys to version control * **Implement retries**: Use exponential backoff for failed requests * **Monitor rate limits**: Track your API usage to avoid limits * **Handle errors gracefully**: Implement proper error handling ### Request Management * **Set appropriate timeouts**: Balance urgency with reviewer availability * **Use webhooks**: More efficient than polling for status updates * **Provide context**: Include relevant metadata to help reviewers * **Choose response types carefully**: Match response type to your use case ## SDKs and Integration ### Official Libraries * **Python**: `pip install hitl-python` (coming soon) * **Node.js**: `npm install @hitl/node` (coming soon) * **Go**: `go get github.com/hitl-sh/go-client` (coming soon) ### No-Code Integrations * **n8n**: Available in the n8n community library * **Zapier**: Connect HITL.sh to 5000+ apps * **Make**: Visual workflow automation ## Support and Resources Check real-time API health and uptime. Join our Discord community for support. Explore our open source projects. Get help from our support team. ## Next Steps Learn about API keys and security best practices. Start by creating a loop to organize your human reviewers. Use callback URLs to receive notifications when requests complete. # Create Loop Source: https://docs.hitl.sh/api-reference/loops/create-loop POST https://api.hitl.sh/v1/api/loops Create a new human review loop with automatic creator membership, invite code, QR code, and join URL generation Create a new loop to route human review requests. When you create a loop, you automatically become a member and receive an invite code with QR code for sharing with other reviewers. ## Authentication Your API key for authentication ## Body Name of the loop (1-100 characters) Description of the loop's purpose (max 500 characters) Icon identifier for the loop (max 100 characters, e.g., "shield-check", "eye", "thumbs-up") ## Response Whether an error occurred Success message The created loop object with member counts Unique identifier for the loop Name of the loop Description of the loop Icon identifier ID of the loop creator Array of loop members Total number of members Number of pending invitations ISO timestamp of creation ISO timestamp of last update Generated invite code for joining the loop Base64 encoded QR code image URL to access the QR code image Direct URL for joining the loop ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops \ -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check" }' ``` ```python Python theme={null} import requests url = "https://api.hitl.sh/v1/api/loops" headers = { "Authorization": "Bearer your_api_key_here", "Content-Type": "application/json" } data = { "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check" } response = requests.post(url, headers=headers, json=data) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const response = await axios.post('https://api.hitl.sh/v1/api/loops', { name: 'Content Moderation Review', description: 'Review user-generated content for community guidelines compliance', icon: 'shield-check' }, { headers: { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json' } }); console.log(response.data); ``` ```json Response theme={null} { "error": false, "msg": "Loop created successfully with QR code", "data": { "loop": { "id": "65f1234567890abcdef12345", "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check", "creator_id": "65f1234567890abcdef12346", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-15T10:30:00Z" } ], "member_count": 1, "pending_count": 0, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:30:00Z" }, "invite_code": "ABC123DEF", "qr_code_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", "qr_code_url": "https://api.hitl.sh/qr/ABC123DEF.png", "join_url": "https://my.hitl.sh/join/ABC123DEF" } } ``` ## Use Cases ### Content Moderation Create a loop for reviewing flagged user content: ```json theme={null} { "name": "Content Moderation", "description": "Review flagged posts and comments for policy violations", "icon": "shield-exclamation" } ``` ### Document Approval Create a loop for business document approvals: ```json theme={null} { "name": "Contract Review", "description": "Legal review of customer contracts and agreements", "icon": "document-check" } ``` ### Quality Assurance Create a loop for AI output validation: ```json theme={null} { "name": "AI Content QA", "description": "Quality assurance for AI-generated content and responses", "icon": "beaker" } ``` ## Next Steps After creating a loop: 1. **Share the invite code** or QR code with your team members 2. **Create your first request** using the [Create Request](/api-reference/requests/create-request) endpoint 3. **Set up webhooks** to receive notifications when requests are completed Learn how to create requests within your newly created loop. # Delete Loop Source: https://docs.hitl.sh/api-reference/loops/delete-loop DELETE https://api.hitl.sh/v1/api/loops/{id} Permanently delete a loop and all associated data. Only the loop creator can delete loops. This action cannot be undone. Permanently delete a loop and all its associated data including members, requests, and history. This is a destructive operation that cannot be undone. This action permanently deletes the loop and all associated data including: * All loop members and their membership history * All requests created within the loop * All responses and feedback from reviewers * All analytics and performance data **This operation cannot be undone.** ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the loop to delete ## Response Whether an error occurred Success message confirming deletion ID of the deleted loop for confirmation ```bash cURL theme={null} curl -X DELETE https://api.hitl.sh/v1/api/loops/65f1234567890abcdef12345 \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests loop_id = "65f1234567890abcdef12345" url = f"https://api.hitl.sh/v1/api/loops/{loop_id}" headers = { "Authorization": "Bearer your_api_key_here" } # Confirm deletion intent confirmation = input(f"Are you sure you want to delete loop {loop_id}? (yes/no): ") if confirmation.lower() == 'yes': response = requests.delete(url, headers=headers) print(response.json()) else: print("Deletion cancelled") ``` ```javascript Node.js theme={null} const axios = require('axios'); const loopId = '65f1234567890abcdef12345'; // Confirm deletion intent const confirmed = confirm(`Are you sure you want to delete loop ${loopId}?`); if (confirmed) { const response = await axios.delete( `https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); console.log(response.data); } else { console.log('Deletion cancelled'); } ``` ```json Response theme={null} { "error": false, "msg": "Loop deleted successfully", "data": { "loop_id": "65f1234567890abcdef12345" } } ``` ## Pre-Deletion Considerations ### What Gets Deleted When you delete a loop, the following data is permanently removed: * Loop name, description, and icon * Creation and update timestamps * Loop creator information * All loop settings and metadata * All member relationships * Member join dates and status * Member activity history within the loop * Invitation records and pending invitations * All requests created within the loop * Request content, priority, and configuration * Request status and lifecycle information * Broadcast and notification history * All reviewer responses and decisions * Response timestamps and metadata * Feedback and rating information * Performance analytics and metrics ### Data That Survives Deletion User accounts of loop members remain intact. Only the membership relationship is removed. Your overall API usage statistics are preserved for billing and analytics. High-level audit logs may be retained for security and compliance purposes. ## Safety Checks ### Before Deleting a Loop Implement these safety checks in your code: ```python Python theme={null} def safe_delete_loop(loop_id, require_confirmation=True): """Safely delete a loop with pre-flight checks""" # Get loop information first response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) if response.status_code != 200: return {"error": True, "msg": "Loop not found or inaccessible"} loop_data = response.json()["data"]["loop"] # Check for active members active_members = loop_data["member_count"] - loop_data["pending_count"] if active_members > 1: # More than just the creator print(f"Warning: Loop has {active_members} active members") # Check for recent activity (optional: get requests) requests_response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers=headers) if requests_response.status_code == 200: requests_data = requests_response.json()["data"]["requests"] recent_requests = [r for r in requests_data if r["status"] in ["pending", "claimed"]] if recent_requests: print(f"Warning: Loop has {len(recent_requests)} active requests") # Confirmation if require_confirmation: confirmation = input(f"Delete loop '{loop_data['name']}'? This cannot be undone. (yes/no): ") if confirmation.lower() != 'yes': return {"error": False, "msg": "Deletion cancelled by user"} # Proceed with deletion delete_response = requests.delete(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) return delete_response.json() ``` ```javascript Node.js theme={null} async function safeDeleteLoop(loopId, requireConfirmation = true) { try { // Get loop information first const loopResponse = await axios.get(`https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); const loopData = loopResponse.data.data.loop; // Check for active members const activeMembers = loopData.member_count - loopData.pending_count; if (activeMembers > 1) { console.warn(`Loop has ${activeMembers} active members`); } // Check for active requests try { const requestsResponse = await axios.get(`https://api.hitl.sh/v1/api/loops/${loopId}/requests`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); const requests = requestsResponse.data.data.requests; const activeRequests = requests.filter(r => ['pending', 'claimed'].includes(r.status)); if (activeRequests.length > 0) { console.warn(`Loop has ${activeRequests.length} active requests`); } } catch (error) { console.log('Could not check requests (may not have access)'); } // Confirmation if (requireConfirmation) { const confirmed = confirm(`Delete loop '${loopData.name}'? This cannot be undone.`); if (!confirmed) { return { error: false, msg: 'Deletion cancelled by user' }; } } // Proceed with deletion const deleteResponse = await axios.delete(`https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); return deleteResponse.data; } catch (error) { return { error: true, msg: error.response?.data?.msg || 'Failed to delete loop' }; } } ``` ## Bulk Operations ### Delete Multiple Loops Handle multiple loop deletions with error handling: ```python theme={null} def bulk_delete_loops(loop_ids, dry_run=True): """Delete multiple loops with safety checks""" results = [] for loop_id in loop_ids: try: if dry_run: # Just check if we can access the loop response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) if response.status_code == 200: loop_data = response.json()["data"]["loop"] results.append({ "loop_id": loop_id, "name": loop_data["name"], "members": loop_data["member_count"], "status": "ready_to_delete" if dry_run else "deleted" }) else: results.append({ "loop_id": loop_id, "error": response.json()["msg"], "status": "error" }) else: # Actually delete the loop response = requests.delete(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) if response.status_code == 200: results.append({ "loop_id": loop_id, "status": "deleted", "deleted_at": response.json()["data"]["deleted_at"] }) else: results.append({ "loop_id": loop_id, "error": response.json()["msg"], "status": "error" }) except Exception as e: results.append({ "loop_id": loop_id, "error": str(e), "status": "exception" }) return results # Usage loop_ids = ["65f1234567890abcdef12345", "65f1234567890abcdef12346"] # Dry run first print("Dry run results:") dry_results = bulk_delete_loops(loop_ids, dry_run=True) for result in dry_results: print(f" {result}") # Actual deletion (uncomment to execute) # print("\nActual deletion:") # actual_results = bulk_delete_loops(loop_ids, dry_run=False) ``` ## Access Control ### Who Can Delete Loops Only the user who created the loop can delete it. This is strictly enforced by checking the `creator_id`. API keys can only delete loops created by the same user account that owns the API key. Loop members cannot delete loops, even if they are administrators in the mobile app. ## Error Handling ### Common Error Scenarios ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Causes:** * Invalid loop ID * Loop already deleted * User doesn't own the loop ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` **Cause:** Only loop creators can delete loops. ```json theme={null} { "error": true, "msg": "Cannot delete loop with active requests" } ``` **Cause:** Some implementations may prevent deletion of loops with pending or claimed requests. **Solution:** Cancel or complete all active requests first. ## Alternatives to Deletion ### Archive Instead of Delete Consider these alternatives before permanent deletion: ```python Python theme={null} def archive_loop(loop_id): """Archive a loop by updating its name instead of deleting""" # Get current loop data response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) loop_data = response.json()["data"]["loop"] # Update with archived naming archived_name = f"[ARCHIVED] {loop_data['name']}" archived_description = f"ARCHIVED on {datetime.now().strftime('%Y-%m-%d')} - {loop_data.get('description', '')}" update_response = requests.put( f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers, json={ "name": archived_name, "description": archived_description } ) return update_response.json() ``` ```javascript Node.js theme={null} async function archiveLoop(loopId) { // Get current loop data const response = await axios.get(`https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); const loopData = response.data.data.loop; // Update with archived naming const archivedName = `[ARCHIVED] ${loopData.name}`; const archivedDescription = `ARCHIVED on ${new Date().toISOString().split('T')[0]} - ${loopData.description || ''}`; const updateResponse = await axios.put( `https://api.hitl.sh/v1/api/loops/${loopId}`, { name: archivedName, description: archivedDescription }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } } ); return updateResponse.data; } ``` ## Recovery Options ### No Recovery Available Once a loop is deleted, there is **no way to recover** the data. The deletion is immediate and permanent. ### Prevention Strategies 1. **Export Data First**: Before deletion, export important data 2. **Use Staging Loops**: Test with non-production loops 3. **Archive Instead**: Consider archiving rather than deleting 4. **Team Review**: Have deletions reviewed by team members ## Next Steps Create a new loop to replace the deleted one if needed. Learn how to export request and response data before deletion. Return to loop management and view your remaining loops. # Get Loop by ID Source: https://docs.hitl.sh/api-reference/loops/get-loop GET https://api.hitl.sh/v1/api/loops/{id} Retrieve detailed information about a specific loop including members, statistics, and current status Get comprehensive details about a specific loop by its ID. This endpoint provides complete loop information including member details, statistics, and metadata. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the loop (24-character MongoDB ObjectID) ## Response Whether an error occurred Success message Complete loop information Unique identifier for the loop Name of the loop Description of the loop Icon identifier ID of the loop creator Array of loop members with status ID of the member user Email address of the member Member status (pending, active) ISO timestamp when member joined (null for pending) Total number of members in the loop Number of pending member invitations ISO timestamp of loop creation ISO timestamp of last update ```bash cURL theme={null} curl -X GET https://api.hitl.sh/v1/api/loops/65f1234567890abcdef12345 \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests loop_id = "65f1234567890abcdef12345" url = f"https://api.hitl.sh/v1/api/loops/{loop_id}" headers = { "Authorization": "Bearer your_api_key_here" } response = requests.get(url, headers=headers) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const loopId = '65f1234567890abcdef12345'; const response = await axios.get(`https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } }); console.log(response.data); ``` ```json Response theme={null} { "error": false, "msg": "Loop retrieved successfully", "data": { "loop": { "id": "65f1234567890abcdef12345", "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check", "creator_id": "65f1234567890abcdef12346", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-15T10:30:00Z" }, { "user_id": "65f1234567890abcdef12347", "email": "reviewer1@example.com", "status": "active", "joined_at": "2024-03-15T11:45:00Z" }, { "user_id": "65f1234567890abcdef12348", "email": "reviewer2@example.com", "status": "pending", "joined_at": null } ], "member_count": 3, "pending_count": 1, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T11:45:00Z" } } } ``` ## Loop Information Overview ### Member Status Types Users who have accepted the invitation and can receive review requests. Users who have been invited but haven't joined the loop yet. ### Loop Statistics This endpoint provides key metrics about your loop: * **Total Members**: Complete count of all loop participants * **Active Members**: Users ready to receive review requests * **Pending Invitations**: Outstanding invitations that need acceptance * **Activity Timeline**: Creation and last update timestamps ## Access Control ### Who Can View Loops Can view complete loop details including all member information and statistics. Can view basic loop information but with limited member details (via mobile app). API keys can only access loops created by the same user account. ## Use Cases ### Monitoring Loop Health Check if your loop has enough active members: ```python theme={null} def check_loop_health(loop_id, min_active_members=2): response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) loop_data = response.json()["data"]["loop"] active_members = loop_data["member_count"] - loop_data["pending_count"] if active_members < min_active_members: print(f"Warning: Loop only has {active_members} active members") return False return True ``` ### Member Management Track pending invitations: ```javascript theme={null} function checkPendingInvitations(loopData) { const pendingMembers = loopData.members.filter(member => member.status === 'pending'); if (pendingMembers.length > 0) { console.log(`${pendingMembers.length} pending invitations:`); pendingMembers.forEach(member => { console.log(`- ${member.email} (invited but not joined)`); }); } return pendingMembers; } ``` ### Loop Analytics Calculate member join rate: ```python theme={null} from datetime import datetime def calculate_join_rate(loop_data): total_members = len(loop_data["members"]) active_members = sum(1 for m in loop_data["members"] if m["status"] == "active") if total_members == 0: return 0 join_rate = (active_members / total_members) * 100 return round(join_rate, 2) # Usage loop_data = response.json()["data"]["loop"] join_rate = calculate_join_rate(loop_data) print(f"Member join rate: {join_rate}%") ``` ## Error Handling ### Common Error Scenarios ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Causes:** * Invalid loop ID format * Loop doesn't exist * Loop was deleted * User doesn't have access to the loop ```json theme={null} { "error": true, "msg": "Invalid loop ID format" } ``` **Cause:** Loop ID must be a valid 24-character MongoDB ObjectID (hexadecimal). **Valid format:** `65f1234567890abcdef12345` ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` **Cause:** API key doesn't have permission to access this loop. Only the loop creator can access loop details via API. ## Integration Examples ### Loop Status Dashboard Build a dashboard to monitor all your loops: ```python theme={null} def get_loop_dashboard(): # Get all loops loops_response = requests.get("https://api.hitl.sh/v1/api/loops", headers=headers) loops = loops_response.json()["data"]["loops"] dashboard = [] for loop in loops: # Get detailed info for each loop detail_response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop['id']}", headers=headers) loop_detail = detail_response.json()["data"]["loop"] dashboard.append({ "name": loop_detail["name"], "id": loop_detail["id"], "active_members": loop_detail["member_count"] - loop_detail["pending_count"], "pending_members": loop_detail["pending_count"], "created": loop_detail["created_at"], "health": "healthy" if (loop_detail["member_count"] - loop_detail["pending_count"]) >= 2 else "needs_attention" }) return dashboard ``` ### Automated Member Follow-up Send reminders to pending members: ```javascript theme={null} async function followUpPendingMembers(loopId) { const response = await fetch(`https://api.hitl.sh/v1/api/loops/${loopId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }); const loopData = await response.json(); const loop = loopData.data.loop; const pendingMembers = loop.members.filter(member => member.status === 'pending'); for (const member of pendingMembers) { // Send reminder email (implement your email service) await sendReminderEmail({ to: member.email, subject: `Reminder: Join ${loop.name} loop`, loopName: loop.name, joinUrl: `https://my.hitl.sh/join/${loop.invite_code}` }); } return `Sent reminders to ${pendingMembers.length} pending members`; } ``` ## Next Steps Modify loop details like name, description, or icon. View detailed member information and manage loop membership. Start creating human review requests within this loop. # Get User Loops Source: https://docs.hitl.sh/api-reference/loops/get-loops GET https://api.hitl.sh/v1/api/loops Retrieve all loops created by the authenticated user with member counts and status information Get a list of all loops you have created. This endpoint returns comprehensive information about each loop including member statistics and current status. ## Authentication Your API key for authentication ## Response Whether an error occurred Success message Array of loop objects Unique identifier for the loop Name of the loop Description of the loop Icon identifier ID of the loop creator Array of loop members with status Total number of members in the loop Number of pending member invitations ISO timestamp of creation ISO timestamp of last update Total number of loops returned ```bash cURL theme={null} curl -X GET https://api.hitl.sh/v1/api/loops \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests url = "https://api.hitl.sh/v1/api/loops" headers = { "Authorization": "Bearer your_api_key_here" } response = requests.get(url, headers=headers) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const response = await axios.get('https://api.hitl.sh/v1/api/loops', { headers: { 'Authorization': 'Bearer your_api_key_here' } }); console.log(response.data); ``` ```json Response theme={null} { "error": false, "msg": "Loops retrieved successfully", "data": { "loops": [ { "id": "65f1234567890abcdef12345", "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check", "creator_id": "65f1234567890abcdef12346", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-15T10:30:00Z" }, { "user_id": "65f1234567890abcdef12347", "email": "reviewer1@example.com", "status": "active", "joined_at": "2024-03-15T11:45:00Z" }, { "user_id": "65f1234567890abcdef12348", "email": "reviewer2@example.com", "status": "pending", "joined_at": null } ], "member_count": 3, "pending_count": 1, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T11:45:00Z" }, { "id": "65f1234567890abcdef12349", "name": "Document Approval", "description": "Legal review of customer contracts", "icon": "document-check", "creator_id": "65f1234567890abcdef12346", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-14T09:15:00Z" }, { "user_id": "65f1234567890abcdef12350", "email": "legal@example.com", "status": "active", "joined_at": "2024-03-14T14:30:00Z" } ], "member_count": 2, "pending_count": 0, "created_at": "2024-03-14T09:15:00Z", "updated_at": "2024-03-14T14:30:00Z" } ], "count": 2 } } ``` ## Understanding Loop Status ### Member Status Types Users who have joined the loop and can receive review requests. Users who have been invited but haven't joined the loop yet. ### Loop Information Each loop provides: * **Member statistics** to understand team capacity * **Creation and update timestamps** for tracking * **Complete member list** with join status * **Creator information** for ownership ## Filtering and Management Use this endpoint to: * **Monitor loop health** by checking member counts * **Identify inactive loops** with no recent updates * **Track team growth** through member statistics * **Manage loop portfolios** across different use cases ## Next Steps Retrieve detailed information about a specific loop by ID. Modify loop details like name, description, or icon. Get detailed member information and manage loop membership. # Loop Members Source: https://docs.hitl.sh/api-reference/loops/loop-members GET https://api.hitl.sh/v1/api/loops/{id}/members Retrieve detailed member information and manage loop membership. Includes member status, join dates, and activity metrics. Get comprehensive information about all members in a loop, including their status, join dates, and activity metrics. This endpoint also provides member management capabilities. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the loop ## Response Whether an error occurred Success message ID of the loop Array of member objects with detailed information Unique identifier of the member user Email address of the member Member status (pending, active) ISO timestamp when member joined (null for pending members) Total number of members in the loop ```bash cURL theme={null} curl -X GET https://api.hitl.sh/v1/api/loops/65f1234567890abcdef12345/members \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests loop_id = "65f1234567890abcdef12345" url = f"https://api.hitl.sh/v1/api/loops/{loop_id}/members" headers = { "Authorization": "Bearer your_api_key_here" } response = requests.get(url, headers=headers) members_data = response.json() # Calculate active and pending counts members = members_data['data']['members'] active_count = sum(1 for m in members if m['status'] == 'active') pending_count = sum(1 for m in members if m['status'] == 'pending') print(f"Total members: {members_data['data']['member_count']}") print(f"Active members: {active_count}") print(f"Pending members: {pending_count}") for member in members: status_indicator = "✅" if member['status'] == 'active' else "⏳" print(f"{status_indicator} {member['email']} ({member['status']})") ``` ```javascript Node.js theme={null} const axios = require('axios'); async function getLoopMembers(loopId) { const response = await axios.get( `https://api.hitl.sh/v1/api/loops/${loopId}/members`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); const data = response.data.data; const activeCount = data.members.filter(m => m.status === 'active').length; const pendingCount = data.members.filter(m => m.status === 'pending').length; console.log(`Total members: ${data.member_count}`); console.log(`Active: ${activeCount}, Pending: ${pendingCount}`); data.members.forEach(member => { const statusIcon = member.status === 'active' ? '✅' : '⏳'; console.log(`${statusIcon} ${member.email} (${member.status})`); }); return data; } getLoopMembers('65f1234567890abcdef12345'); ``` ```json Response theme={null} { "error": false, "msg": "Loop members retrieved successfully", "data": { "loop_id": "65f1234567890abcdef12345", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-15T10:30:00Z" }, { "user_id": "65f1234567890abcdef12347", "email": "reviewer1@example.com", "status": "active", "joined_at": "2024-03-15T11:45:00Z" }, { "user_id": "65f1234567890abcdef12348", "email": "reviewer2@example.com", "status": "active", "joined_at": "2024-03-15T12:15:00Z" }, { "user_id": "65f1234567890abcdef12349", "email": "pending@example.com", "status": "pending", "joined_at": null } ], "member_count": 4 } } ``` ## Member Status Types **Status**: `active` * Have accepted the invitation * Can receive review requests * Can respond to requests * Counted in capacity planning **Status**: `pending` * Invitation sent but not accepted * Cannot receive review requests * `joined_at` is null * Need follow-up for activation ## Member Analytics ### Activity Tracking Monitor member engagement and performance: ```python theme={null} def analyze_member_activity(members_data): """Analyze member activity patterns""" members = members_data["data"]["members"] active_members = [m for m in members if m["status"] == "active"] pending_members = [m for m in members if m["status"] == "pending"] analysis = { "total_members": len(members), "activation_rate": (len(active_members) / len(members)) * 100 if members else 0, "pending_followup_needed": len(pending_members), "recently_active": [] } # Check recent activity (last 24 hours) from datetime import datetime, timedelta cutoff_time = datetime.now() - timedelta(hours=24) for member in active_members: if member.get("last_active"): last_active = datetime.fromisoformat(member["last_active"].replace("Z", "+00:00")) if last_active > cutoff_time: analysis["recently_active"].append(member["email"]) return analysis ``` ### Capacity Planning Determine if you have enough active reviewers: ```javascript theme={null} function assessLoopCapacity(membersData, minRequired = 3) { const data = membersData.data; const activeCount = data.active_count; const pendingCount = data.pending_count; return { currentCapacity: activeCount, minimumRequired: minRequired, isAdequate: activeCount >= minRequired, potentialCapacity: activeCount + pendingCount, recommendations: { needMoreMembers: activeCount < minRequired, followUpPending: pendingCount > 0, optimalSize: activeCount >= minRequired && activeCount <= 8 } }; } // Usage const capacity = assessLoopCapacity(membersData, 3); if (!capacity.isAdequate) { console.log(`⚠️ Need ${minRequired - capacity.currentCapacity} more active members`); } ``` ## Remove Member Remove a member from the loop. Only loop creators can remove members, and the creator cannot remove themselves. **Endpoint:** `DELETE /v1/api/loops/{id}/members/{userId}` ### Path Parameters * **id** (string, required): The unique identifier of the loop * **userId** (string, required): The unique identifier of the user to remove ### Example Request ```bash cURL theme={null} curl -X DELETE https://api.hitl.sh/v1/api/loops/65f1234567890abcdef12345/members/65f1234567890abcdef12347 \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests loop_id = "65f1234567890abcdef12345" user_id = "65f1234567890abcdef12347" url = f"https://api.hitl.sh/v1/api/loops/{loop_id}/members/{user_id}" headers = { "Authorization": "Bearer your_api_key_here" } response = requests.delete(url, headers=headers) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const loopId = '65f1234567890abcdef12345'; const userId = '65f1234567890abcdef12347'; const response = await axios.delete( `https://api.hitl.sh/v1/api/loops/${loopId}/members/${userId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); console.log(response.data); ``` ### Response ```json theme={null} { "error": false, "msg": "Member removed successfully", "data": { "loop_id": "65f1234567890abcdef12345", "user_id": "65f1234567890abcdef12347" } } ``` ### Error Responses **Member not found:** ```json theme={null} { "error": true, "msg": "User is not a member of this loop" } ``` **Cannot remove creator:** ```json theme={null} { "error": true, "msg": "Loop creator cannot remove themselves" } ``` **Only creator can remove members:** ```json theme={null} { "error": true, "msg": "Only loop creator can remove members" } ``` ## Member Management Use Cases ### Onboarding New Members Track and follow up on pending invitations: ```python theme={null} def follow_up_pending_members(loop_id, days_threshold=3): """Find pending members who need follow-up""" response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}/members", headers=headers) members_data = response.json()["data"]["members"] pending_members = [m for m in members_data if m["status"] == "pending"] # In a real implementation, you'd check invitation timestamps # For now, we'll assume all pending members need follow-up followup_actions = [] for member in pending_members: followup_actions.append({ "email": member["email"], "action": "send_reminder", "days_pending": "unknown" # Would calculate from invitation date }) return followup_actions # Usage followups = follow_up_pending_members("65f1234567890abcdef12345") for action in followups: print(f"Send reminder to {action['email']}") ``` ### Member Performance Monitoring Track member activity and engagement: ```javascript theme={null} async function getMemberPerformance(loopId) { try { // Get members const membersResponse = await axios.get( `https://api.hitl.sh/v1/api/loops/${loopId}/members`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); // Get loop requests for performance analysis const requestsResponse = await axios.get( `https://api.hitl.sh/v1/api/loops/${loopId}/requests`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const members = membersResponse.data.data.members; const requests = requestsResponse.data.data.requests; // Analyze performance const performance = members.map(member => { const memberRequests = requests.filter(r => r.response_by === member.user_id && r.status === 'completed' ); return { email: member.email, status: member.status, responses_completed: memberRequests.length, avg_response_time: calculateAverageResponseTime(memberRequests), last_active: member.last_active }; }); return performance; } catch (error) { console.error('Error getting member performance:', error); return []; } } function calculateAverageResponseTime(requests) { if (requests.length === 0) return 0; const totalTime = requests.reduce((sum, request) => { return sum + (request.response_time_seconds || 0); }, 0); return Math.round(totalTime / requests.length); } ``` ### Bulk Member Operations Manage multiple members efficiently: ```python theme={null} def audit_loop_membership(loop_ids): """Audit membership across multiple loops""" audit_results = [] for loop_id in loop_ids: try: # Get loop info loop_response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) loop_data = loop_response.json()["data"]["loop"] # Get members members_response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}/members", headers=headers) members_data = members_response.json()["data"] # Analyze membership health health_score = calculate_membership_health(members_data) audit_results.append({ "loop_id": loop_id, "loop_name": loop_data["name"], "total_members": members_data["total_count"], "active_members": members_data["active_count"], "pending_members": members_data["pending_count"], "health_score": health_score, "recommendations": get_membership_recommendations(members_data) }) except Exception as e: audit_results.append({ "loop_id": loop_id, "error": str(e), "status": "failed" }) return audit_results def calculate_membership_health(members_data): """Calculate membership health score (0-100)""" total = members_data["total_count"] active = members_data["active_count"] if total == 0: return 0 # Base score on activation rate activation_rate = (active / total) * 100 # Adjust for absolute numbers if active < 2: activation_rate *= 0.5 # Penalize loops with too few active members elif active > 8: activation_rate = min(activation_rate, 85) # Cap score for very large loops return round(activation_rate) def get_membership_recommendations(members_data): """Get actionable recommendations for membership""" recommendations = [] if members_data["active_count"] < 2: recommendations.append("Add more active members for redundancy") if members_data["pending_count"] > 0: recommendations.append(f"Follow up on {members_data['pending_count']} pending invitations") if members_data["active_count"] == 0: recommendations.append("URGENT: No active members - loop cannot process requests") activation_rate = (members_data["active_count"] / members_data["total_count"]) * 100 if activation_rate < 50: recommendations.append("Low activation rate - review invitation process") return recommendations ``` ## Access Control ### Member Visibility Can see all member details including email addresses, join dates, and activity information. API keys can only view members of loops created by the same user account. Sensitive member information is only visible to loop creators for privacy protection. ### Member Management Permissions Currently done through invite codes and QR codes. Direct member addition via API may be added in future. Only loop creators can remove members. The creator cannot be removed from their own loop. Future feature: Different member roles (admin, reviewer, observer) with varying permissions. ## Error Handling ```json theme={null} { "error": true, "msg": "Loop not found" } ``` ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` ```json theme={null} { "error": true, "msg": "Member not found in this loop" } ``` ```json theme={null} { "error": true, "msg": "Loop creator cannot be removed" } ``` ## Best Practices ### Member Onboarding 1. **Clear Invitations**: Include context about the loop purpose in invitations 2. **Follow-up Strategy**: Contact pending members after 2-3 days 3. **Backup Reviewers**: Maintain at least 3 active members for redundancy 4. **Performance Tracking**: Monitor response times and engagement ### Capacity Management 1. **Right-size Teams**: 3-8 active members for most use cases 2. **Monitor Workload**: Ensure requests are distributed evenly 3. **Plan for Growth**: Add members before hitting capacity limits 4. **Regular Audits**: Review membership quarterly ## Next Steps Now that you understand your team, create requests for them to review. Learn how members join loops through mobile invitations. # Update Loop Source: https://docs.hitl.sh/api-reference/loops/update-loop PUT https://api.hitl.sh/v1/api/loops/{id} Update an existing loop's name, description, or icon. Only the loop creator can make updates. Update the details of an existing loop. You can modify the name, description, and icon of any loop you've created. This operation preserves all members and existing requests. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the loop to update ## Body Parameters New name for the loop (1-100 characters) New description of the loop's purpose (max 500 characters) New icon identifier for the loop All body parameters are optional. Only provide the fields you want to update. ## Response Whether an error occurred Success message The updated loop object with all current information Unique identifier for the loop Updated name of the loop Updated description of the loop Updated icon identifier ID of the loop creator (unchanged) Array of loop members (unchanged) Total number of members Number of pending invitations ISO timestamp of creation (unchanged) ISO timestamp of this update ```bash cURL theme={null} curl -X PUT https://api.hitl.sh/v1/api/loops/65f1234567890abcdef12345 \ -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Enhanced Content Moderation", "description": "Advanced review system for user-generated content with improved guidelines", "icon": "shield-exclamation" }' ``` ```python Python theme={null} import requests loop_id = "65f1234567890abcdef12345" url = f"https://api.hitl.sh/v1/api/loops/{loop_id}" headers = { "Authorization": "Bearer your_api_key_here", "Content-Type": "application/json" } data = { "name": "Enhanced Content Moderation", "description": "Advanced review system with improved guidelines", "icon": "shield-exclamation" } response = requests.put(url, headers=headers, json=data) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const loopId = '65f1234567890abcdef12345'; const response = await axios.put( `https://api.hitl.sh/v1/api/loops/${loopId}`, { name: 'Enhanced Content Moderation', description: 'Advanced review system with improved guidelines', icon: 'shield-exclamation' }, { headers: { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json' } } ); console.log(response.data); ``` ```json Response theme={null} { "error": false, "msg": "Loop updated successfully", "data": { "loop": { "id": "65f1234567890abcdef12345", "name": "Enhanced Content Moderation", "description": "Advanced review system for user-generated content with improved guidelines", "icon": "shield-exclamation", "creator_id": "65f1234567890abcdef12346", "members": [ { "user_id": "65f1234567890abcdef12346", "email": "creator@example.com", "status": "active", "joined_at": "2024-03-15T10:30:00Z" }, { "user_id": "65f1234567890abcdef12347", "email": "reviewer1@example.com", "status": "active", "joined_at": "2024-03-15T11:45:00Z" } ], "member_count": 2, "pending_count": 0, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T14:20:00Z" } } } ``` ## Update Scenarios ### Name Only Update Update just the loop name: ```json theme={null} { "name": "Content Review V2" } ``` ### Description Only Update Update just the description: ```json theme={null} { "description": "Updated workflow for reviewing user submissions with new compliance requirements" } ``` ### Icon Only Update Update just the icon: ```json theme={null} { "icon": "shield-alt" } ``` ### Partial Update Update multiple fields (but not all): ```json theme={null} { "name": "Premium Content Review", "icon": "star" } ``` ## Available Icons shield-check, shield-alt, shield-exclamation, lock, key document-text, document-check, file-text, clipboard, edit eye, search, thumbs-up, thumbs-down, star badge-check, certificate, award, beaker, cog chat, comment, bell, megaphone, mail folder, tag, flag, bookmark, heart ## Validation Rules ### Name Validation * **Required**: No (optional for updates) * **Min length**: 1 character * **Max length**: 100 characters * **Allowed**: Letters, numbers, spaces, and basic punctuation ### Description Validation * **Required**: No (optional for updates) * **Max length**: 500 characters * **Allowed**: All characters including newlines ### Icon Validation * **Required**: No (optional for updates) * **Format**: String identifier * **Validation**: Must be a valid icon identifier from available icons ## Use Cases ### Rebranding Loops Update loop name and icon for rebranding: ```python theme={null} def rebrand_loop(loop_id, new_name, new_icon): url = f"https://api.hitl.sh/v1/api/loops/{loop_id}" data = { "name": new_name, "icon": new_icon } response = requests.put(url, headers=headers, json=data) if response.status_code == 200: loop_data = response.json()["data"]["loop"] print(f"Successfully rebranded loop to '{loop_data['name']}'") return loop_data else: print(f"Failed to rebrand loop: {response.json()['msg']}") return None ``` ### Improving Descriptions Add more detailed descriptions to existing loops: ```javascript theme={null} async function improveLoopDescription(loopId, newDescription) { try { const response = await axios.put( `https://api.hitl.sh/v1/api/loops/${loopId}`, { description: newDescription }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } } ); console.log('Description updated successfully'); return response.data.data.loop; } catch (error) { console.error('Failed to update description:', error.response?.data?.msg); throw error; } } // Usage await improveLoopDescription( 'loopId', 'Comprehensive content moderation with AI pre-filtering and human oversight for policy violations' ); ``` ### Bulk Loop Updates Update multiple loops with a consistent naming scheme: ```python theme={null} def standardize_loop_names(loop_prefix="Review"): # Get all loops loops_response = requests.get("https://api.hitl.sh/v1/api/loops", headers=headers) loops = loops_response.json()["data"]["loops"] updated_loops = [] for loop in loops: # Skip loops that already follow the naming convention if loop["name"].startswith(loop_prefix): continue new_name = f"{loop_prefix} - {loop['name']}" update_response = requests.put( f"https://api.hitl.sh/v1/api/loops/{loop['id']}", headers=headers, json={"name": new_name} ) if update_response.status_code == 200: updated_loops.append(update_response.json()["data"]["loop"]) return updated_loops ``` ## Access Control ### Who Can Update Loops Only the user who created the loop can update it. This is enforced by checking the `creator_id` against the authenticated user. API keys can only update loops created by the same user account that owns the API key. Regular loop members cannot update loop details. They can only participate in reviews. ## Error Handling ### Common Error Scenarios ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Causes:** * Invalid loop ID * Loop was deleted * User doesn't own the loop ```json theme={null} { "error": true, "msg": "Access denied to this loop" } ``` **Cause:** Only loop creators can update loops. ```json theme={null} { "error": true, "msg": "Validation failed", "data": "name must be between 1 and 100 characters" } ``` **Causes:** * Name too long (>100 characters) * Name empty (if provided) * Description too long (>500 characters) * Invalid icon identifier ```json theme={null} { "error": true, "msg": "At least one field must be provided for update" } ``` **Cause:** Request body is empty or contains no valid update fields. ## Best Practices ### Versioning Updates Track major changes with version numbers in names: ```python theme={null} def version_loop_update(loop_id, updates, version=None): if version: if 'name' in updates: updates['name'] = f"{updates['name']} v{version}" updates['description'] = f"{updates.get('description', '')} (Updated to v{version})" return requests.put(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers, json=updates) ``` ### Gradual Updates Update loops incrementally rather than all at once: ```javascript theme={null} async function updateLoopGradually(loopId, updates) { const updateOrder = ['description', 'icon', 'name']; // Update description first, name last for (const field of updateOrder) { if (updates[field]) { await axios.put( `https://api.hitl.sh/v1/api/loops/${loopId}`, { [field]: updates[field] }, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); // Small delay between updates await new Promise(resolve => setTimeout(resolve, 100)); } } } ``` ### Backup Before Updates Keep a record of previous state: ```python theme={null} def safe_loop_update(loop_id, updates): # Get current state current_response = requests.get(f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers) backup = current_response.json()["data"]["loop"] # Perform update update_response = requests.put( f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers=headers, json=updates ) if update_response.status_code == 200: return { "success": True, "updated": update_response.json()["data"]["loop"], "backup": backup } else: return { "success": False, "error": update_response.json()["msg"], "backup": backup } ``` ## Next Steps Learn how to delete loops when they're no longer needed. Add or remove members from your updated loop. See all requests that have been created within this loop. # Add Request Feedback Source: https://docs.hitl.sh/api-reference/requests/add-feedback POST https://api.hitl.sh/v1/api/requests/{id}/feedback Provide feedback on completed requests to improve reviewer performance and system quality over time Add feedback to completed requests to help improve reviewer performance and overall system quality. Feedback can include ratings, comments, and specific quality metrics that help reviewers understand what worked well and what could be improved. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the completed request ## Body Parameters Feedback object containing ratings, comments, and quality metrics ### Feedback Object Structure The feedback object can contain any combination of the following fields: Overall quality rating (1-5 scale, where 5 is excellent) Free-form text feedback and comments (max 1000 characters) Response accuracy rating (1-5 scale) Response timeliness rating (1-5 scale) Response helpfulness rating (1-5 scale) Whether you would recommend this reviewer for similar requests Array of feedback tags (e.g., \["thorough", "quick", "insightful"]) Whether this request requires follow-up action Feedback category: "positive", "constructive", "issue" ## Response Whether an error occurred Success message ID of the request that received feedback The feedback object that was submitted ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/requests/65f1234567890abcdef12348/feedback \ -H "Authorization: Bearer your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "feedback": { "rating": 5, "comment": "Excellent review! Very thorough analysis and quick turnaround time.", "accuracy": 5, "timeliness": 5, "helpfulness": 4, "would_recommend": true, "tags": ["thorough", "quick", "professional"], "category": "positive" } }' ``` ```python Python theme={null} import requests def add_request_feedback(request_id, feedback_data): """Add comprehensive feedback to a completed request""" url = f"https://api.hitl.sh/v1/api/requests/{request_id}/feedback" headers = { "Authorization": "Bearer your_api_key_here", "Content-Type": "application/json" } # Validate feedback data if "rating" in feedback_data: if not (1 <= feedback_data["rating"] <= 5): raise ValueError("Rating must be between 1 and 5") if "comment" in feedback_data: if len(feedback_data["comment"]) > 1000: raise ValueError("Comment must be 1000 characters or less") payload = {"feedback": feedback_data} response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: result = response.json() print(f"✅ Feedback submitted successfully") print(f" Feedback ID: {result['data']['feedback_id']}") return result else: error_msg = response.json().get("msg", "Unknown error") print(f"❌ Failed to submit feedback: {error_msg}") return {"error": True, "msg": error_msg} # Example usage feedback = { "rating": 5, "comment": "Excellent work! Very detailed analysis and spot-on recommendations.", "accuracy": 5, "timeliness": 4, "helpfulness": 5, "would_recommend": True, "tags": ["thorough", "insightful", "professional"], "category": "positive" } result = add_request_feedback("65f1234567890abcdef12348", feedback) ``` ```javascript Node.js theme={null} const axios = require('axios'); class FeedbackManager { constructor(apiKey) { this.apiKey = apiKey; this.baseURL = 'https://api.hitl.sh/v1'; } async addFeedback(requestId, feedbackData) { try { // Validate feedback data this.validateFeedback(feedbackData); const response = await axios.post( `${this.baseURL}/requests/${requestId}/feedback`, { feedback: feedbackData }, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } } ); console.log('✅ Feedback submitted successfully'); return response.data; } catch (error) { console.error('❌ Failed to submit feedback:', error.response?.data?.msg); throw error; } } validateFeedback(feedback) { // Validate rating fields const ratingFields = ['rating', 'accuracy', 'timeliness', 'helpfulness']; ratingFields.forEach(field => { if (feedback[field] !== undefined) { if (!Number.isInteger(feedback[field]) || feedback[field] < 1 || feedback[field] > 5) { throw new Error(`${field} must be an integer between 1 and 5`); } } }); // Validate comment length if (feedback.comment && feedback.comment.length > 1000) { throw new Error('Comment must be 1000 characters or less'); } // Validate category if (feedback.category) { const validCategories = ['positive', 'constructive', 'issue']; if (!validCategories.includes(feedback.category)) { throw new Error('Category must be one of: positive, constructive, issue'); } } } // Helper method for quick positive feedback async addPositiveFeedback(requestId, comment, rating = 5) { return this.addFeedback(requestId, { rating, comment, category: 'positive', would_recommend: true }); } // Helper method for constructive feedback async addConstructiveFeedback(requestId, comment, suggestions) { return this.addFeedback(requestId, { rating: 3, comment, category: 'constructive', tags: suggestions, follow_up_needed: true }); } } // Usage const feedbackManager = new FeedbackManager('your_api_key_here'); // Detailed feedback await feedbackManager.addFeedback('65f1234567890abcdef12348', { rating: 4, comment: 'Good analysis but could have been more detailed in the recommendations section.', accuracy: 5, timeliness: 3, helpfulness: 4, would_recommend: true, tags: ['accurate', 'could-be-more-detailed'], category: 'constructive' }); // Quick positive feedback await feedbackManager.addPositiveFeedback( '65f1234567890abcdef12349', 'Perfect response, exactly what we needed!' ); ``` ```json Response theme={null} { "error": false, "msg": "Feedback added successfully", "data": { "request_id": "65f1234567890abcdef12348", "feedback": { "rating": 5, "comment": "Excellent review! Very thorough analysis and quick turnaround time.", "accuracy": 5, "timeliness": 5, "helpfulness": 4, "would_recommend": true, "tags": ["thorough", "quick", "professional"], "category": "positive" } } } ``` ## Feedback Categories Use the `category` field to classify your feedback: Use `positive` for high ratings (4-5) and appreciation. Reinforces desired behaviors and builds reviewer confidence. Use `constructive` for moderate ratings (2-4) with specific improvement suggestions. Educational and growth-oriented feedback. Use `issue` for low ratings (1-2) that identify problems. May require follow-up. Use sparingly for serious issues. ## Feedback Best Practices ### Effective Feedback Structure **Good**: "The analysis was thorough and covered all the key policy violations I mentioned in the request." **Avoid**: "Good job." **Why**: Specific feedback helps reviewers understand exactly what they did well. **Good**: "Great attention to detail in identifying spam patterns. For future requests, could you also mention the confidence level of your assessment?" **Avoid**: "Everything was perfect" or "Everything was wrong." **Why**: Balanced feedback encourages growth while recognizing strengths. **Good**: "The response could benefit from more detailed examples to support the conclusion." **Avoid**: "You're not very thorough." **Why**: Focus on actions and outcomes rather than personal characteristics. **Good**: "Given the urgent nature of this content moderation request, the 15-minute response time was exactly what we needed." **Avoid**: "Too slow." **Why**: Context helps reviewers understand the specific requirements of different request types. ## Feedback Analytics ### Track Feedback Patterns Monitor your feedback trends to improve request quality: ```python theme={null} def analyze_feedback_patterns(): """Analyze feedback patterns across all requests""" # Get all completed requests response = requests.get( "https://api.hitl.sh/v1/api/requests?status=completed&limit=100", headers={"Authorization": "Bearer your_api_key_here"} ) completed_requests = response.json()["data"]["requests"] # Analyze feedback patterns (this would require additional API endpoints) feedback_stats = { "total_requests": len(completed_requests), "feedback_given": 0, "avg_rating": 0, "common_tags": {}, "category_breakdown": {"positive": 0, "constructive": 0, "issue": 0}, "reviewer_performance": {} } # Note: In a real implementation, you'd need endpoints to retrieve # feedback data to perform this analysis return feedback_stats def generate_feedback_suggestions(request_data, response_quality): """Generate feedback suggestions based on request and response analysis""" suggestions = { "recommended_rating": 3, "suggested_tags": [], "feedback_template": "", "category": "constructive" } # Analyze response time response_time = request_data.get("response_time_seconds", 0) if response_time < 300: # Under 5 minutes suggestions["suggested_tags"].append("quick") suggestions["recommended_rating"] += 1 elif response_time > 3600: # Over 1 hour suggestions["feedback_template"] += "Consider faster response times for future requests. " # Analyze priority handling if request_data["priority"] == "critical" and response_time < 600: suggestions["suggested_tags"].append("urgent-handling") suggestions["recommended_rating"] += 1 # Generate template if suggestions["recommended_rating"] >= 4: suggestions["category"] = "positive" suggestions["feedback_template"] = "Great work! " + suggestions["feedback_template"] elif suggestions["recommended_rating"] <= 2: suggestions["category"] = "issue" suggestions["feedback_template"] = "There are some areas for improvement: " + suggestions["feedback_template"] return suggestions ``` ### Bulk Feedback Operations Provide feedback on multiple completed requests: ```javascript theme={null} async function bulkFeedback(requestFeedbackPairs, options = {}) { const { delay = 200, validateFirst = true } = options; const results = []; for (const { requestId, feedback } of requestFeedbackPairs) { try { if (validateFirst) { // Get request details to ensure it's completed const requestResponse = await axios.get( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); const requestData = requestResponse.data.data.request; if (requestData.status !== 'completed') { results.push({ requestId, success: false, error: `Request status is '${requestData.status}', not 'completed'` }); continue; } } // Submit feedback const feedbackResponse = await axios.post( `https://api.hitl.sh/v1/api/requests/${requestId}/feedback`, { feedback }, { headers: { 'Authorization': 'Bearer your_api_key_here', 'Content-Type': 'application/json' } } ); results.push({ requestId, success: true, feedbackId: feedbackResponse.data.data.feedback_id }); // Rate limiting delay if (delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } } catch (error) { results.push({ requestId, success: false, error: error.response?.data?.msg || error.message }); } } return { total: requestFeedbackPairs.length, successful: results.filter(r => r.success).length, failed: results.filter(r => !r.success).length, results }; } // Usage const feedbackBatch = [ { requestId: '65f1234567890abcdef12348', feedback: { rating: 5, comment: 'Excellent work!', category: 'positive' } }, { requestId: '65f1234567890abcdef12349', feedback: { rating: 4, comment: 'Good job, could be more detailed', category: 'constructive' } } ]; const results = await bulkFeedback(feedbackBatch); console.log(`Submitted feedback for ${results.successful}/${results.total} requests`); ``` ## Feedback Templates ### Pre-built Feedback Templates Create reusable feedback templates for common scenarios: ```json theme={null} { "rating": 5, "comment": "Outstanding work! Response was accurate, timely, and exceeded expectations. The detailed analysis and clear recommendations were exactly what we needed.", "accuracy": 5, "timeliness": 5, "helpfulness": 5, "would_recommend": true, "tags": ["excellent", "thorough", "professional"], "category": "positive" } ``` ```json theme={null} { "rating": 4, "comment": "Solid work overall. The analysis was accurate and helpful. For future requests, consider providing more specific examples to support your conclusions.", "accuracy": 5, "timeliness": 4, "helpfulness": 4, "would_recommend": true, "tags": ["accurate", "could-add-examples"], "category": "constructive" } ``` ```json theme={null} { "rating": 2, "comment": "The response addressed the basic question but lacked the depth and detail specified in the request. Please review the requirements more carefully for future requests.", "accuracy": 3, "timeliness": 3, "helpfulness": 2, "would_recommend": false, "tags": ["incomplete", "needs-detail"], "category": "issue", "follow_up_needed": true } ``` ```json theme={null} { "rating": 5, "comment": "Perfect response! Quick, accurate, and exactly what we needed. Thank you!", "timeliness": 5, "would_recommend": true, "tags": ["quick", "accurate"], "category": "positive" } ``` ## Access Control ### Feedback Permissions Only the API key that created the request can provide feedback on it. Feedback can only be added to requests with status "completed". Currently, each request can receive one feedback entry. Future versions may support multiple feedback entries or feedback updates. ## Error Handling ### Common Error Scenarios ```json theme={null} { "error": true, "msg": "Feedback can only be added to completed requests" } ``` **Cause:** Request status is not "completed". **Solution:** Wait for request completion or check request status. ```json theme={null} { "error": true, "msg": "Rating values must be between 1 and 5" } ``` **Cause:** Rating fields contain values outside the 1-5 range. **Solution:** Ensure all rating fields are integers between 1 and 5. ```json theme={null} { "error": true, "msg": "Comment exceeds maximum length of 1000 characters" } ``` **Cause:** Feedback comment is longer than 1000 characters. **Solution:** Shorten the comment or split into multiple sentences. ```json theme={null} { "error": true, "msg": "Request not found" } ``` **Cause:** Invalid request ID or request doesn't belong to your API key. **Solution:** Verify the request ID and ownership. ## Impact of Feedback ### On Reviewers Feedback helps reviewers understand what works well and what needs improvement in their review process. Positive feedback boosts reviewer motivation and recognizes good work. Constructive feedback provides specific areas for improvement and learning opportunities. Consistent feedback helps establish and maintain quality standards across the reviewer team. ### On System Quality Feedback data can be used to better match requests with appropriate reviewers based on past performance. Aggregate feedback provides insights into overall system performance and areas for improvement. Feedback patterns help identify bottlenecks and optimization opportunities in the review process. ## Next Steps Review your request history and identify requests that could benefit from feedback. Learn how reviewers see and respond to feedback on the mobile app. # Cancel Request Source: https://docs.hitl.sh/api-reference/requests/cancel-request DELETE https://api.hitl.sh/v1/api/requests/{id} Cancel a pending request. Only requests that haven't been completed can be cancelled. Cancel an active request before it's completed. This is useful when a request is no longer needed or when you want to modify the request parameters. Only pending requests can be cancelled. Cancelled requests cannot be restored. If you need the same request processed, you'll need to create a new one. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the request to cancel ## Response Whether an error occurred Success message confirming cancellation ID of the cancelled request for confirmation New status of the request (always "cancelled") ```bash cURL theme={null} curl -X DELETE https://api.hitl.sh/v1/api/requests/65f1234567890abcdef12348 \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests def cancel_request(request_id, confirm=True): """Cancel a request with optional confirmation""" if confirm: # Get request details first get_response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": "Bearer your_api_key_here"} ) if get_response.status_code == 200: request_data = get_response.json()["data"]["request"] print(f"Request to cancel:") print(f" ID: {request_data['id']}") print(f" Status: {request_data['status']}") print(f" Text: {request_data['request_text'][:100]}...") confirmation = input("Are you sure you want to cancel this request? (yes/no): ") if confirmation.lower() != 'yes': return {"cancelled": False, "reason": "User cancelled operation"} # Proceed with cancellation url = f"https://api.hitl.sh/v1/api/requests/{request_id}" headers = {"Authorization": "Bearer your_api_key_here"} response = requests.delete(url, headers=headers) if response.status_code == 200: return response.json() else: return {"error": True, "msg": response.json().get("msg", "Cancellation failed")} # Usage result = cancel_request("65f1234567890abcdef12348") if not result.get("error"): print(f"Request cancelled successfully: {result['data']['request_id']}") else: print(f"Failed to cancel: {result['msg']}") ``` ```javascript Node.js theme={null} const axios = require('axios'); async function cancelRequest(requestId, options = {}) { const { confirm = true, reason = null } = options; try { if (confirm) { // Get request details for confirmation const getResponse = await axios.get( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); const requestData = getResponse.data.data.request; console.log(`About to cancel request:`); console.log(` ID: ${requestData.id}`); console.log(` Status: ${requestData.status}`); console.log(` Priority: ${requestData.priority}`); // In a real app, you'd show a confirmation dialog const confirmed = confirm(`Cancel request ${requestId}?`); if (!confirmed) { return { cancelled: false, reason: 'User cancelled operation' }; } } // Proceed with cancellation const response = await axios.delete( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' }, data: reason ? { reason } : undefined } ); console.log(`✅ Request cancelled successfully`); return response.data; } catch (error) { console.error(`❌ Failed to cancel request:`, error.response?.data?.msg); return { error: true, msg: error.response?.data?.msg || 'Cancellation failed' }; } } // Usage examples await cancelRequest('65f1234567890abcdef12348'); await cancelRequest('65f1234567890abcdef12349', { confirm: false, reason: 'Request no longer needed' }); ``` ```json Success Response theme={null} { "error": false, "msg": "Request cancelled successfully", "data": { "request_id": "65f1234567890abcdef12348", "status": "cancelled" } } ``` ## Cancellation Rules ### What Can Be Cancelled **Status**: `pending` * Waiting for reviewer response * Can be cancelled without impact ### What Cannot Be Cancelled **Status**: `completed` * Reviewer has submitted response * Use feedback system instead * Response data is final **Status**: `timeout`, `cancelled` * Already in final state * No further action possible * Historical record preserved ## Impact of Cancellation ### On API Usage **Refund Policy**: Usually refunded to API quota * Request never reached a reviewer * No processing time invested * Full refund to your hourly limit **Example**: If you've used 45/100 API calls this hour and cancel a pending request, you'll have 46/100 remaining. ### On Reviewers Cancelling pending requests has minimal impact on reviewers: * Request is removed from their queue * No wasted reviewer effort * Keeps the review queue clean * Cancel requests as soon as you know they're not needed * Review request parameters carefully before creating * Avoid frequent cancellations to maintain workflow efficiency ## Cancellation Strategies ### Batch Cancellation Cancel multiple requests efficiently: ```python Python theme={null} def batch_cancel_requests(request_ids, max_concurrent=5): """Cancel multiple requests with rate limiting""" import concurrent.futures import time def cancel_single_request(request_id): try: response = requests.delete( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": "Bearer your_api_key_here"} ) if response.status_code == 200: data = response.json()["data"] return { "request_id": request_id, "status": "cancelled" } else: return { "request_id": request_id, "status": "error", "error": response.json().get("msg", "Unknown error") } except Exception as e: return { "request_id": request_id, "status": "exception", "error": str(e) } results = [] # Process in batches to respect rate limits with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent) as executor: # Submit all requests future_to_id = { executor.submit(cancel_single_request, req_id): req_id for req_id in request_ids } for future in concurrent.futures.as_completed(future_to_id): result = future.result() results.append(result) # Small delay to avoid hitting rate limits time.sleep(0.1) # Summarize results summary = { "total_requests": len(request_ids), "successfully_cancelled": sum(1 for r in results if r["status"] == "cancelled"), "errors": [r for r in results if r["status"] in ["error", "exception"]], "refunds_granted": sum(1 for r in results if r.get("refunded", False)), "details": results } return summary # Usage request_ids = ["65f1234567890abcdef12348", "65f1234567890abcdef12349", "65f1234567890abcdef12350"] results = batch_cancel_requests(request_ids) print(f"Cancelled {results['successfully_cancelled']}/{results['total_requests']} requests") print(f"Refunds granted: {results['refunds_granted']}") for error in results['errors']: print(f"Error cancelling {error['request_id']}: {error['error']}") ``` ```javascript Node.js theme={null} async function batchCancelRequests(requestIds, maxConcurrent = 5) { const results = []; const semaphore = new Semaphore(maxConcurrent); const cancelPromises = requestIds.map(async (requestId) => { await semaphore.acquire(); try { const response = await axios.delete( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); return { requestId, status: 'cancelled' }; } catch (error) { return { requestId, status: 'error', error: error.response?.data?.msg || error.message }; } finally { semaphore.release(); // Small delay to respect rate limits await new Promise(resolve => setTimeout(resolve, 100)); } }); const results = await Promise.all(cancelPromises); // Summarize results const summary = { totalRequests: requestIds.length, successfullyCancelled: results.filter(r => r.status === 'cancelled').length, errors: results.filter(r => r.status === 'error'), refundsGranted: results.filter(r => r.refunded).length, details: results }; return summary; } // Simple semaphore implementation for rate limiting class Semaphore { constructor(count) { this.count = count; this.waiting = []; } async acquire() { return new Promise(resolve => { if (this.count > 0) { this.count--; resolve(); } else { this.waiting.push(resolve); } }); } release() { this.count++; if (this.waiting.length > 0) { const resolve = this.waiting.shift(); this.count--; resolve(); } } } ``` ### Smart Cancellation Logic Implement intelligent cancellation based on request status: ```python theme={null} def smart_cancel_requests(filter_criteria): """Cancel requests based on smart criteria""" # Get requests matching criteria params = {} if filter_criteria.get('status'): params['status'] = filter_criteria['status'] if filter_criteria.get('priority'): params['priority'] = filter_criteria['priority'] if filter_criteria.get('created_before'): params['created_before'] = filter_criteria['created_before'] response = requests.get( "https://api.hitl.sh/v1/api/requests", headers={"Authorization": "Bearer your_api_key_here"}, params=params ) requests_to_cancel = response.json()["data"]["requests"] # Smart filtering smart_candidates = [] for req in requests_to_cancel: should_cancel = False reason = "" # Only cancel if safe to do so if req["status"] == "pending": # Check if request is old and likely not urgent created_at = datetime.fromisoformat(req["created_at"].replace("Z", "+00:00")) age_hours = (datetime.now(timezone.utc) - created_at).total_seconds() / 3600 if age_hours > 24 and req["priority"] in ["low", "medium"]: should_cancel = True reason = f"Old {req['priority']} priority request ({age_hours:.1f}h old)" if should_cancel: smart_candidates.append({ "request_id": req["id"], "reason": reason, "status": req["status"], "priority": req["priority"], "age_hours": age_hours }) # Show candidates and get confirmation if not smart_candidates: return {"message": "No requests meet smart cancellation criteria"} print(f"Found {len(smart_candidates)} requests for smart cancellation:") for candidate in smart_candidates: print(f" {candidate['request_id']}: {candidate['reason']}") confirmation = input(f"\nCancel {len(smart_candidates)} requests? (yes/no): ") if confirmation.lower() != 'yes': return {"cancelled": False, "reason": "User cancelled operation"} # Cancel the selected requests cancel_results = batch_cancel_requests([c["request_id"] for c in smart_candidates]) cancel_results["smart_criteria"] = smart_candidates return cancel_results ``` ## Error Handling ### Common Error Scenarios ```json theme={null} { "error": true, "msg": "Request not found" } ``` **Causes:** * Invalid request ID * Request doesn't exist * Request doesn't belong to your API key ```json theme={null} { "error": true, "msg": "Request cannot be cancelled in current state" } ``` **Cause:** Request is already completed, timed out, or cancelled. **Solution:** Use the feedback endpoint for completed requests instead. ```json theme={null} { "error": true, "msg": "Access denied to this request" } ``` **Cause:** Request was created by a different API key. **Solution:** Ensure you're using the correct API key that created the request. ### Error Recovery Handle cancellation errors gracefully: ```javascript theme={null} async function safeCancel(requestId, retries = 2) { for (let attempt = 0; attempt <= retries; attempt++) { try { const response = await axios.delete( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); return { success: true, data: response.data }; } catch (error) { const errorMsg = error.response?.data?.msg; // Don't retry certain errors if (errorMsg?.includes('cannot be cancelled') || errorMsg?.includes('not found') || errorMsg?.includes('access denied')) { return { success: false, error: errorMsg, retryable: false }; } // Retry for network errors or rate limits if (attempt < retries) { const delay = Math.pow(2, attempt) * 1000; // Exponential backoff console.log(`Retrying cancellation in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); continue; } return { success: false, error: errorMsg || 'Cancellation failed after retries', retryable: true }; } } } ``` ## Alternative Actions ### Instead of Cancelling Consider these alternatives: Consider waiting for completion and then providing feedback on the response quality. Create a new request with improved parameters rather than cancelling and losing the original request history. Some use cases: Add additional context to help reviewers rather than cancelling. (Note: This feature may be added in the future) Future feature: Adjust request priority instead of cancelling. (This feature may be added in the future) ## Best Practices ### When to Cancel 1. **Early Cancellation**: Cancel as soon as you know the request is no longer needed 2. **Batch Processing**: If cancelling multiple requests, use batch operations 3. **Communication**: Provide cancellation reasons when possible (future feature) 4. **Timing**: Avoid cancelling requests that are likely to be completed soon ### When Not to Cancel 1. **High Priority**: Don't cancel urgent requests without good reason 2. **Near Completion**: If a request is likely to complete soon, wait instead 3. **Frequent Pattern**: Avoid creating a pattern of frequent cancellations ## Next Steps Create a new request to replace the cancelled one with improved parameters. Learn how to provide feedback on completed requests instead of cancelling. Track your request patterns to optimize future request parameters. # Create Request Source: https://docs.hitl.sh/api-reference/requests/create-request POST https://api.hitl.sh/v1/api/loops/{loopId}/requests Create a new human review request within a loop and broadcast it to active members with push notifications Create a new request that will be sent to all active members of a loop for human review. The request supports multiple content types, response configurations, and notification systems. ## Quick Start for Common Use Cases Perfect for content moderation, document approval, or basic yes/no decisions. ```json theme={null} { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this content for approval", "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "Approve"}, {"value": "reject", "label": "Reject"} ], "required": true }, "default_response": "reject", "platform": "api" } ``` Rate content quality, AI outputs, or customer service interactions. ```json theme={null} { "processing_type": "time-sensitive", "type": "markdown", "priority": "low", "request_text": "Rate the quality of this AI response on a scale of 1-5", "timeout_seconds": 3600, "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1, "required": true }, "default_response": 3, "platform": "api" } ``` Get written feedback, suggestions, or explanations from reviewers. ```json theme={null} { "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": "Provide feedback on this article draft", "response_type": "text", "response_config": { "placeholder": "Provide your detailed feedback here...", "min_length": 50, "max_length": 1000, "required": true }, "default_response": "No feedback provided", "platform": "api" } ``` Identify multiple issues, features, or categories in content. ```json theme={null} { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "What issues do you see in this content? Select all that apply:", "timeout_seconds": 2400, "response_type": "multi_select", "response_config": { "options": [ {"value": "grammar", "label": "Grammar Issues"}, {"value": "factual", "label": "Factual Errors"}, {"value": "structure", "label": "Poor Structure"}, {"value": "none", "label": "No Issues Found"} ], "min_selections": 1, "max_selections": 4, "required": true }, "default_response": "none", "platform": "api" } ``` Send an AI-generated draft for a human to polish before delivery. Perfect for notifications, emails, or any message that benefits from a final human touch. ```json theme={null} { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review and edit this AI-generated client notification before it is sent.", "timeout_seconds": 3600, "response_type": "editable_text", "response_config": { "draft_text": "Dear Client,\n\nYour project milestone has been completed ahead of schedule. Our team will reach out shortly to discuss next steps.\n\nBest regards,\nThe Team", "placeholder": "Edit the message above...", "min_length": 20, "max_length": 2000, "required": true }, "default_response": "Notification not reviewed within timeout period", "platform": "api" } ``` ## Authentication Your API key for authentication ## Path Parameters The ID of the loop where the request will be created ## Body Parameters Processing urgency type
Options: `time-sensitive`, `deferred`
When `processing_type` is set to `"time-sensitive"`, the `timeout_seconds` parameter becomes **required**. Time-sensitive requests must specify an explicit timeout. Content type of the request
Options: `markdown`, `image`, `file`, `video`, `audio`
Priority level of the request
Options: `low`, `medium`, `high`, `critical`
The main content of the request (1-2000 characters) URL of the image to review (required when `type` is `image`) Array of image URLs for multi-image requests URL of the document to review (required when `type` is `file`) MIME type of the document, e.g. `application/pdf` (required when `type` is `file`) Display name shown to reviewers (required when `type` is `file`) Array of document URLs for multi-document requests MIME types for each document in `file_urls` Display names for each document in `file_urls` URL of the video to review (required when `type` is `video`) Array of video URLs for multi-video requests URL of the audio to review (required when `type` is `audio`) Array of audio URLs for multi-audio requests Additional context data for the request (any valid JSON object) Timeout in seconds (60-86400)
⚠️ REQUIRED when processing\_type is "time-sensitive"
Optional for: `deferred` requests (default: 30 days)
Type of response expected from reviewers
Options: `text`, `editable_text`, `single_select`, `multi_select`, `rating`, `number`
Configuration for the response type (varies by response\_type)
📖 See examples: Response Types Guide
Default response value if timeout occurs Platform creating the request
Options: `n8n`, `zapier`, `web_portal`, `api`, `mobile`, `webhook`
Version of the platform used URL to call when request is completed (webhook) ## Response Whether an error occurred Success message ID of the created request Current status of the request (always "pending" on creation) Processing urgency type Content type of the request Priority level of the request ISO timestamp when the request times out Number of users who received the request notification Number of successful push notifications sent URL for polling request status ## Response Type Quick Reference For complete configuration options and advanced examples, visit the [Response Types Guide](/responses/types). ### Single Select - Choose One Option ```json theme={null} { "response_config": { "options": [ {"value": "approve", "label": "Approve"}, {"value": "reject", "label": "Reject"}, {"value": "needs_changes", "label": "Needs Changes"} ], "required": true }, "default_response": "reject" } ``` ### Multi Select - Choose Multiple Options ```json theme={null} { "response_config": { "options": [ {"value": "policy_violation", "label": "Policy Violation"}, {"value": "spam", "label": "Spam Content"}, {"value": "inappropriate", "label": "Inappropriate Content"} ], "min_selections": 1, "max_selections": 3, "required": true }, "default_response": "policy_violation" } ``` ### Rating - Numeric Scale ```json theme={null} { "response_config": { "scale_max": 5 }, "default_response": 3 } ``` **Note:** Only `scale_max` is required. Optional fields with defaults: * `scale_min`: defaults to 1 * `scale_step`: defaults to 1 * `required`: defaults to false ### Number - Numeric Input ```json theme={null} { "response_config": { "max_value": 100 }, "default_response": 50 } ``` **Note:** Only `max_value` is required. Optional fields with defaults: * `min_value`: defaults to 1 * `decimal_places`: defaults to 2 * `allow_negative`: defaults to false * `required`: defaults to false ### Text - Free Form Input ```json theme={null} { "response_config": {}, "default_response": "No issues found" } ``` Visit our complete Response Types Guide for advanced configurations, validation rules, and best practices. **Ready to test?** 1. Get your API key from [app.hitl.sh](https://app.hitl.sh) 2. Create a loop and copy the Loop ID 3. Replace `YOUR_API_KEY` and `YOUR_LOOP_ID` in the examples below 4. The request will be sent to all active members in your loop for review ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this content", "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "Approve"}, {"value": "reject", "label": "Reject"} ], "required": true }, "default_response": "reject", "platform": "api" }' ``` ```python Python theme={null} import requests # Replace with your actual values API_KEY = "YOUR_API_KEY" LOOP_ID = "YOUR_LOOP_ID" response = requests.post( f"https://api.hitl.sh/v1/api/loops/{LOOP_ID}/requests", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this content", "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "Approve"}, {"value": "reject", "label": "Reject"} ], "required": true }, "default_response": "reject", "platform": "api" } ) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); // Replace with your actual values const API_KEY = 'YOUR_API_KEY'; const LOOP_ID = 'YOUR_LOOP_ID'; const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${LOOP_ID}/requests`, { processing_type: 'time-sensitive', type: 'markdown', priority: 'medium', request_text: 'Please review this content', timeout_seconds: 1800, response_type: 'single_select', response_config: { options: [ {value: 'approve', label: 'Approve'}, {value: 'reject', label: 'Reject'} ], required: true }, default_response: 'reject', platform: 'api' }, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); console.log(response.data); ``` ```json Response theme={null} { "error": false, "msg": "Request created and broadcasted successfully", "data": { "request_id": "65f1234567890abcdef12348", "status": "pending", "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "timeout_at": "2024-03-15T11:30:00Z", "broadcasted_to": 4, "notifications_sent": 3, "polling_url": "/v1/api/requests/65f1234567890abcdef12348" } } ``` ## Quick Test Examples Copy any of these examples to test different response types: Perfect for yes/no decisions or selecting one option. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Should we approve this content?", "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": [ {"value": "yes", "label": "Yes, approve it"}, {"value": "no", "label": "No, reject it"} ], "required": true }, "default_response": "no", "platform": "api" }' ``` For selecting multiple issues or features. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "What issues do you see? Select all that apply:", "timeout_seconds": 1800, "response_type": "multi_select", "response_config": { "options": [ {"value": "grammar", "label": "Grammar issues"}, {"value": "tone", "label": "Wrong tone"}, {"value": "factual", "label": "Factual errors"}, {"value": "none", "label": "No issues"} ], "min_selections": 1, "max_selections": 4, "required": true }, "default_response": "none", "platform": "api" }' ``` For quality ratings and scores. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Rate this content quality (1-5 scale):", "timeout_seconds": 1800, "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1, "required": true }, "default_response": 3, "platform": "api" }' ``` For quantities, counts, or measurements. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "How many errors do you count?", "timeout_seconds": 1800, "response_type": "number", "response_config": { "max_value": 100, "min_value": 0, "decimal_places": 0 }, "default_response": 0, "platform": "api" }' ``` For detailed feedback and explanations. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": "Please provide detailed feedback:", "response_type": "text", "response_config": {}, "default_response": "No feedback provided", "platform": "api" }' ``` For AI-generated messages that need human review and revision before sending. ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review and edit this AI-generated client notification before it is sent.", "timeout_seconds": 3600, "response_type": "editable_text", "response_config": { "draft_text": "Dear Client,\n\nYour project milestone has been completed ahead of schedule. Our team will reach out shortly to discuss next steps.\n\nBest regards,\nThe Team", "placeholder": "Edit the message above...", "min_length": 20, "max_length": 2000, "required": true }, "default_response": "Notification not reviewed within timeout period", "platform": "api" }' ``` ## Use Cases ### Content Moderation Review flagged user-generated content: ```json theme={null} { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Review this comment for policy violations", "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": ["Approve", "Remove", "Shadow Ban"] }, "default_response": "Approve", "platform": "api" } ``` ### Image Review Review images for appropriate content: ```json theme={null} { "processing_type": "time-sensitive", "type": "image", "priority": "high", "request_text": "Review this uploaded image for inappropriate content", "image_url": "https://example.com/uploads/image123.jpg", "timeout_seconds": 900, "response_type": "single_select", "response_config": { "options": ["Approve", "Reject"] }, "default_response": "Reject", "platform": "api" } ``` ### Quality Rating Rate AI-generated content quality: ```json theme={null} { "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": "Rate the quality of this AI-generated response", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1, "required": true }, "default_response": 3, "platform": "api" } ``` ## Request Lifecycle 1. **Creation** - Request is created and assigned a unique ID 2. **Broadcasting** - Push notifications sent to all active loop members 3. **Pending** - Waiting for a reviewer to respond 4. **Completed** - Reviewer has submitted their response 5. **Webhook** - Callback URL is notified (if configured) ## Next Steps Check the status and response of your request using the polling URL. Configure webhooks to receive real-time notifications when requests complete. Cancel a pending request if it's no longer needed. # Get Request Source: https://docs.hitl.sh/api-reference/requests/get-request GET https://api.hitl.sh/v1/api/requests/{id} Retrieve detailed information about a specific request including status, response data, and broadcast results Get comprehensive information about a specific request. This endpoint is useful for polling request status and retrieving the human reviewer's response once completed. ## Authentication Your API key for authentication ## Path Parameters The unique identifier of the request ## Response Whether an error occurred Success message The complete request object Unique identifier for the request ID of the loop this request belongs to ID of the user who created the request ID of the API key used to create the request Processing urgency type (`time-sensitive`, `deferred`) Content type (`markdown`, `image`) Priority level (`low`, `medium`, `high`, `critical`) The main content of the request URL of the image to review (if applicable) Additional context data Platform that created the request Version of the platform used Type of response expected Configuration for the response type Default response if timeout occurs ISO timestamp when the request times out URL to call when request is completed List of users who received the request notification ISO timestamp when the request was broadcasted Current status (`pending`, `completed`, `timeout`, `cancelled`) ID of the user who responded (if completed) ISO timestamp when the response was submitted The actual response data from the reviewer Time taken to respond in seconds ISO timestamp when the request was created ISO timestamp when the request was last updated ```bash cURL theme={null} curl -X GET https://api.hitl.sh/v1/api/requests/65f1234567890abcdef12348 \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests url = "https://api.hitl.sh/v1/api/requests/65f1234567890abcdef12348" headers = { "Authorization": "Bearer your_api_key_here" } response = requests.get(url, headers=headers) print(response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); const response = await axios.get( 'https://api.hitl.sh/v1/api/requests/65f1234567890abcdef12348', { headers: { 'Authorization': 'Bearer your_api_key_here' } } ); console.log(response.data); ``` ```json Pending Request theme={null} { "error": false, "msg": "Request retrieved successfully", "data": { "request": { "id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "creator_id": "65f1234567890abcdef12346", "api_key_id": "65f1234567890abcdef12349", "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user-generated content for community guidelines compliance.", "image_url": null, "context": { "user_id": "user123", "post_id": "post456", "automated_flags": ["potential_spam"] }, "platform": "api", "platform_version": "1.0.0", "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Changes"] }, "default_response": "Approve", "timeout_at": "2024-03-15T11:30:00Z", "callback_url": "https://example.com/webhook/response", "broadcasted_to": [ { "user_id": "65f1234567890abcdef12350", "email": "reviewer1@example.com", "role": "member", "notification_sent": true, "notification_error": null }, { "user_id": "65f1234567890abcdef12351", "email": "reviewer2@example.com", "role": "member", "notification_sent": true, "notification_error": null } ], "broadcasted_at": "2024-03-15T10:30:00Z", "status": "pending", "response_by": null, "response_at": null, "response_data": null, "response_time_seconds": null, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:30:00Z" } } } ``` ```json Completed Request theme={null} { "error": false, "msg": "Request retrieved successfully", "data": { "request": { "id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "creator_id": "65f1234567890abcdef12346", "api_key_id": "65f1234567890abcdef12349", "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user-generated content for community guidelines compliance.", "image_url": null, "context": { "user_id": "user123", "post_id": "post456", "automated_flags": ["potential_spam"] }, "platform": "api", "platform_version": "1.0.0", "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Changes"] }, "default_response": "Approve", "timeout_at": "2024-03-15T11:30:00Z", "callback_url": "https://example.com/webhook/response", "broadcasted_to": [ { "user_id": "65f1234567890abcdef12350", "email": "reviewer1@example.com", "role": "member", "notification_sent": true, "notification_error": null } ], "broadcasted_at": "2024-03-15T10:30:00Z", "status": "completed", "response_by": "65f1234567890abcdef12350", "response_at": "2024-03-15T10:45:00Z", "response_data": "Approve", "response_time_seconds": 900.5, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:45:00Z" } } } ``` ## Request Status Types Request is waiting for a reviewer response. No response yet. Reviewer has submitted their response. Check `response_data` field. Request was cancelled before completion. Request timed out. `default_response` was used as the final response. ## Polling Strategy ### Basic Polling Poll every 30 seconds for time-sensitive requests: ```python theme={null} import time import requests def poll_request_status(request_id, api_key, max_attempts=120): """Poll request status for up to 1 hour (120 * 30 seconds)""" url = f"https://api.hitl.sh/v1/api/requests/{request_id}" headers = {"Authorization": f"Bearer {api_key}"} for attempt in range(max_attempts): response = requests.get(url, headers=headers) data = response.json() if data["data"]["request"]["status"] in ["completed", "cancelled", "timeout"]: return data["data"]["request"] time.sleep(30) # Wait 30 seconds before next poll return None # Timeout reached ``` ### Exponential Backoff More efficient polling with increasing intervals: ```python theme={null} import time import requests def poll_with_backoff(request_id, api_key): """Poll with exponential backoff""" url = f"https://api.hitl.sh/v1/api/requests/{request_id}" headers = {"Authorization": f"Bearer {api_key}"} intervals = [5, 10, 30, 60, 120, 300] # seconds for interval in intervals: response = requests.get(url, headers=headers) data = response.json() status = data["data"]["request"]["status"] if status in ["completed", "cancelled", "timeout"]: return data["data"]["request"] time.sleep(interval) return None ``` ## Response Data Examples ### Single Select Response ```json theme={null} { "response_data": "Approve" } ``` ### Multi Select Response ```json theme={null} { "response_data": ["Policy Violation", "Spam"] } ``` ### Rating Response ```json theme={null} { "response_data": 4 } ``` ### Text Response ```json theme={null} { "response_data": "The content looks good but could benefit from better formatting." } ``` ### Boolean Response ```json theme={null} { "response_data": true } ``` ## Next Steps Provide feedback on completed requests to improve reviewer performance. Cancel a pending request if it's no longer needed. Get real-time notifications instead of polling for status updates. # Get User Requests Source: https://docs.hitl.sh/api-reference/requests/get-requests GET https://api.hitl.sh/v1/api/requests Retrieve all requests created by your API key with status, response data, and performance metrics Get a comprehensive list of all requests you've created using your API key. This endpoint provides detailed information about each request including current status, response data, and performance metrics. ## Authentication Your API key for authentication ## Query Parameters Filter requests by status
**Options**: `pending`, `completed`, `timeout`, `cancelled`
Filter requests by priority level
**Options**: `low`, `medium`, `high`, `critical`
Filter requests from a specific loop Maximum number of requests to return (1-100, default: 50) Number of requests to skip for pagination (default: 0) Sort order for results
**Options**: `created_at_desc` (default), `created_at_asc`, `priority_desc`, `status_asc`
## Response Whether an error occurred Success message Array of request objects Unique identifier for the request ID of the loop this request belongs to Processing urgency (time-sensitive, deferred) Content type (markdown, image) Priority level (low, medium, high, critical) The request content Expected response type Current request status Response from reviewer (if completed) ID of user who responded (if completed) ISO timestamp of response (if completed) Time taken to respond in seconds (if completed) ISO timestamp when request times out ISO timestamp when request was created ISO timestamp when request was last updated Number of requests returned in this response Total number of requests matching the filter criteria Whether there are more requests to fetch Pagination information Current page size Current offset Offset for next page (if has\_more is true) ```bash cURL theme={null} # Get all requests curl -X GET https://api.hitl.sh/v1/api/requests \ -H "Authorization: Bearer your_api_key_here" # Get pending requests only curl -X GET "https://api.hitl.sh/v1/api/requests?status=pending" \ -H "Authorization: Bearer your_api_key_here" # Get high priority requests with pagination curl -X GET "https://api.hitl.sh/v1/api/requests?priority=high&limit=20&offset=0" \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests def get_requests(status=None, priority=None, loop_id=None, limit=50, offset=0): """Get requests with optional filtering""" url = "https://api.hitl.sh/v1/api/requests" headers = {"Authorization": "Bearer your_api_key_here"} params = {"limit": limit, "offset": offset} if status: params["status"] = status if priority: params["priority"] = priority if loop_id: params["loop_id"] = loop_id response = requests.get(url, headers=headers, params=params) return response.json() # Get all requests all_requests = get_requests() # Get pending requests pending_requests = get_requests(status="pending") # Get high priority requests high_priority = get_requests(priority="high") print(f"Total requests: {all_requests['data']['total']}") print(f"Pending requests: {len([r for r in all_requests['data']['requests'] if r['status'] == 'pending'])}") ``` ```javascript Node.js theme={null} const axios = require('axios'); class RequestsAPI { constructor(apiKey) { this.apiKey = apiKey; this.baseURL = 'https://api.hitl.sh/v1'; } async getRequests(filters = {}) { const params = new URLSearchParams(); // Add filters if (filters.status) params.append('status', filters.status); if (filters.priority) params.append('priority', filters.priority); if (filters.loop_id) params.append('loop_id', filters.loop_id); if (filters.limit) params.append('limit', filters.limit); if (filters.offset) params.append('offset', filters.offset); if (filters.sort) params.append('sort', filters.sort); const response = await axios.get(`${this.baseURL}/requests?${params}`, { headers: { 'Authorization': `Bearer ${this.apiKey}` } }); return response.data; } async getAllRequests() { let allRequests = []; let offset = 0; const limit = 100; do { const response = await this.getRequests({ limit, offset }); allRequests.push(...response.data.requests); offset += limit; if (!response.data.has_more) break; } while (true); return allRequests; } } // Usage const api = new RequestsAPI('your_api_key_here'); // Get pending requests const pendingRequests = await api.getRequests({ status: 'pending' }); console.log(`${pendingRequests.data.count} pending requests found`); // Get all requests with pagination const allRequests = await api.getAllRequests(); console.log(`Total requests retrieved: ${allRequests.length}`); ``` ```json Response theme={null} { "error": false, "msg": "Requests retrieved successfully", "data": { "requests": [ { "id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user-generated content for community guidelines compliance.", "response_type": "single_select", "status": "completed", "response_data": "Approve", "response_by": "65f1234567890abcdef12350", "response_at": "2024-03-15T10:45:00Z", "response_time_seconds": 900.5, "timeout_at": "2024-03-15T11:30:00Z", "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:45:00Z" }, { "id": "65f1234567890abcdef12349", "loop_id": "65f1234567890abcdef12345", "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Rate the quality of this AI-generated response for accuracy and helpfulness.", "response_type": "rating", "status": "pending", "response_data": null, "response_by": null, "response_at": null, "response_time_seconds": null, "timeout_at": "2024-04-14T10:30:00Z", "created_at": "2024-03-15T10:35:00Z", "updated_at": "2024-03-15T10:35:00Z" } ], "count": 2, "total": 15, "has_more": true, "pagination": { "limit": 50, "offset": 0, "next_offset": 50 } } } ``` ## Request Status Overview **Status**: `pending` * Waiting for reviewer response * No response yet * Can be cancelled **Status**: `completed` * Reviewer submitted response * `response_data` contains the answer * Can receive feedback **Status**: `timeout` * Request exceeded timeout period * `default_response` used as final answer * No further action possible **Status**: `cancelled` * Request was cancelled before completion * No response data available * Final status ## Filtering and Sorting ### Advanced Filtering Examples ```python Python theme={null} def get_requests_dashboard(): """Get comprehensive request dashboard data""" api_key = "your_api_key_here" headers = {"Authorization": f"Bearer {api_key}"} base_url = "https://api.hitl.sh/v1/api/requests" dashboard_data = {} # Get requests by status statuses = ["pending", "completed", "timeout", "cancelled"] for status in statuses: response = requests.get(f"{base_url}?status={status}", headers=headers) data = response.json()["data"] dashboard_data[f"{status}_requests"] = { "count": data["total"], "requests": data["requests"][:5] # First 5 for preview } # Get high priority pending requests urgent_response = requests.get( f"{base_url}?status=pending&priority=high&sort=created_at_asc", headers=headers ) dashboard_data["urgent_pending"] = urgent_response.json()["data"]["requests"] # Get recent completions recent_response = requests.get( f"{base_url}?status=completed&sort=created_at_desc&limit=10", headers=headers ) dashboard_data["recent_completions"] = recent_response.json()["data"]["requests"] return dashboard_data ``` ```javascript Node.js theme={null} async function getRequestAnalytics() { const apiKey = 'your_api_key_here'; const headers = { 'Authorization': `Bearer ${apiKey}` }; const baseURL = 'https://api.hitl.sh/v1/api/requests'; try { // Parallel requests for different metrics const [allRequests, pendingRequests, completedRequests] = await Promise.all([ axios.get(baseURL, { headers }), axios.get(`${baseURL}?status=pending`, { headers }), axios.get(`${baseURL}?status=completed&limit=100`, { headers }) ]); const analytics = { total_requests: allRequests.data.data.total, pending_count: pendingRequests.data.data.total, completed_count: completedRequests.data.data.total, completion_rate: 0, avg_response_time: 0, priority_breakdown: { low: 0, medium: 0, high: 0, critical: 0 } }; // Calculate completion rate if (analytics.total_requests > 0) { analytics.completion_rate = (analytics.completed_count / analytics.total_requests) * 100; } // Calculate average response time const completed = completedRequests.data.data.requests; if (completed.length > 0) { const totalTime = completed.reduce((sum, req) => sum + (req.response_time_seconds || 0), 0); analytics.avg_response_time = totalTime / completed.length; } // Priority breakdown allRequests.data.data.requests.forEach(req => { analytics.priority_breakdown[req.priority]++; }); return analytics; } catch (error) { console.error('Error fetching request analytics:', error); return null; } } ``` ## Pagination Best Practices ### Efficient Pagination Handle large datasets efficiently: ```python theme={null} def paginate_all_requests(api_key, batch_size=100): """Generator that yields all requests in batches""" headers = {"Authorization": f"Bearer {api_key}"} url = "https://api.hitl.sh/v1/api/requests" offset = 0 while True: params = {"limit": batch_size, "offset": offset} response = requests.get(url, headers=headers, params=params) data = response.json()["data"] # Yield current batch for request in data["requests"]: yield request # Check if we have more data if not data["has_more"]: break offset += batch_size # Usage request_count = 0 for request in paginate_all_requests("your_api_key"): request_count += 1 if request["status"] == "pending": print(f"Pending request: {request['id']}") print(f"Total requests processed: {request_count}") ``` ### Cursor-based Pagination (Alternative) For very large datasets, implement cursor-based pagination: ```javascript theme={null} class RequestsPaginator { constructor(apiKey, filters = {}) { this.apiKey = apiKey; this.filters = filters; this.lastCreatedAt = null; } async getNextPage(limit = 50) { const params = new URLSearchParams(); params.append('limit', limit); params.append('sort', 'created_at_desc'); // Add filters Object.keys(this.filters).forEach(key => { if (this.filters[key]) { params.append(key, this.filters[key]); } }); // Cursor pagination using created_at if (this.lastCreatedAt) { params.append('created_before', this.lastCreatedAt); } const response = await axios.get( `https://api.hitl.sh/v1/api/requests?${params}`, { headers: { 'Authorization': `Bearer ${this.apiKey}` } } ); const requests = response.data.data.requests; if (requests.length > 0) { this.lastCreatedAt = requests[requests.length - 1].created_at; } return { requests, hasMore: requests.length === limit }; } } ``` ## Request Performance Analysis ### Response Time Analysis Analyze reviewer performance: ```python theme={null} def analyze_response_times(requests_data): """Analyze response time patterns""" completed_requests = [r for r in requests_data if r["status"] == "completed" and r["response_time_seconds"]] if not completed_requests: return {"error": "No completed requests with response times"} response_times = [r["response_time_seconds"] for r in completed_requests] analysis = { "total_completed": len(completed_requests), "avg_response_time_seconds": sum(response_times) / len(response_times), "min_response_time": min(response_times), "max_response_time": max(response_times), "median_response_time": sorted(response_times)[len(response_times) // 2], "response_time_ranges": { "under_5_min": sum(1 for t in response_times if t < 300), "5_to_30_min": sum(1 for t in response_times if 300 <= t < 1800), "30_min_to_2_hours": sum(1 for t in response_times if 1800 <= t < 7200), "over_2_hours": sum(1 for t in response_times if t >= 7200) } } # Convert seconds to human readable analysis["avg_response_time_formatted"] = format_duration(analysis["avg_response_time_seconds"]) return analysis def format_duration(seconds): """Format seconds into human readable duration""" if seconds < 60: return f"{int(seconds)} seconds" elif seconds < 3600: return f"{int(seconds // 60)} minutes" else: hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) return f"{hours}h {minutes}m" ``` ### Priority Distribution Track request priority patterns: ```javascript theme={null} function analyzePriorityDistribution(requests) { const priorityStats = { low: { count: 0, completed: 0, avg_response_time: 0 }, medium: { count: 0, completed: 0, avg_response_time: 0 }, high: { count: 0, completed: 0, avg_response_time: 0 }, critical: { count: 0, completed: 0, avg_response_time: 0 } }; requests.forEach(request => { const priority = request.priority; priorityStats[priority].count++; if (request.status === 'completed' && request.response_time_seconds) { priorityStats[priority].completed++; priorityStats[priority].avg_response_time += request.response_time_seconds; } }); // Calculate averages Object.keys(priorityStats).forEach(priority => { const stats = priorityStats[priority]; if (stats.completed > 0) { stats.avg_response_time = Math.round(stats.avg_response_time / stats.completed); } stats.completion_rate = stats.completed / stats.count; }); return priorityStats; } ``` ## Export and Reporting ### CSV Export Export request data for analysis: ```python theme={null} import csv from datetime import datetime def export_requests_to_csv(filename="requests_export.csv"): """Export all requests to CSV file""" # Get all requests all_requests = [] offset = 0 limit = 100 while True: response = requests.get( "https://api.hitl.sh/v1/api/requests", headers={"Authorization": "Bearer your_api_key_here"}, params={"limit": limit, "offset": offset} ) data = response.json()["data"] all_requests.extend(data["requests"]) if not data["has_more"]: break offset += limit # Write to CSV with open(filename, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = [ 'id', 'loop_id', 'processing_type', 'type', 'priority', 'request_text', 'response_type', 'status', 'response_data', 'response_time_seconds', 'created_at', 'response_at' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for request in all_requests: # Clean data for CSV row = {field: request.get(field, '') for field in fieldnames} # Truncate long text fields if len(str(row['request_text'])) > 1000: row['request_text'] = str(row['request_text'])[:997] + "..." writer.writerow(row) print(f"Exported {len(all_requests)} requests to {filename}") return filename ``` ## Error Handling ```json theme={null} { "error": true, "msg": "Invalid status filter value", "data": "status must be one of: pending, completed, timeout, cancelled" } ``` ```json theme={null} { "error": true, "msg": "Invalid pagination parameters", "data": "limit must be between 1 and 100" } ``` ```json theme={null} { "error": true, "msg": "API key request limit exceeded", "data": { "usage_count": 100, "usage_limit": 100, "remaining": 0 } } ``` ## Next Steps Retrieve detailed information about a specific request including response data. Create a new human review request within a loop. # Test API Key Source: https://docs.hitl.sh/api-reference/test-api-key Verify that your API key is valid and working correctly. Returns account information and API key status. Verify your API key is working correctly and get information about your account, rate limits, and permissions. This is the first endpoint you should call when integrating with HITL.sh. This endpoint is perfect for debugging authentication issues and monitoring your API usage. ## Authentication Your API key for authentication. Format: `Bearer your_api_key_here` ## Response Whether an error occurred (always false for successful requests) Success message confirming API key validity Unique identifier of the API key being used User ID associated with the API key Email address associated with the account Current account status. Values: `active`, `inactive` Maximum requests per hour for this API key Remaining requests in the current hour ISO timestamp when the rate limit resets List of permissions granted to this API key
Example: `["loops:read", "loops:write", "requests:read", "requests:write"]`
```bash cURL theme={null} curl -X GET https://api.hitl.sh/v1/test \ -H "Authorization: Bearer your_api_key_here" ``` ```python Python theme={null} import requests api_key = "your_api_key_here" headers = { "Authorization": f"Bearer {api_key}" } response = requests.get("https://api.hitl.sh/v1/test", headers=headers) data = response.json() if data["error"] == False: print("✅ API key is valid!") print(f"Account: {data['data']['email']}") print(f"Status: {data['data']['account_status']}") print(f"Rate limit: {data['data']['rate_limit']['remaining']}/{data['data']['rate_limit']['limit']}") else: print("❌ API key validation failed") ``` ```javascript Node.js theme={null} const axios = require('axios'); const apiKey = 'your_api_key_here'; const headers = { 'Authorization': `Bearer ${apiKey}` }; async function testApiKey() { try { const response = await axios.get('https://api.hitl.sh/v1/test', { headers }); const data = response.data; if (!data.error) { console.log('✅ API key is valid!'); console.log(`Account: ${data.data.email}`); console.log(`Status: ${data.data.account_status}`); console.log(`Rate limit: ${data.data.rate_limit.remaining}/${data.data.rate_limit.limit}`); } } catch (error) { console.error('❌ API key validation failed:', error.response?.data); } } testApiKey(); ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) type TestResponse struct { Error bool `json:"error"` Msg string `json:"msg"` Data struct { APIKeyID string `json:"api_key_id"` UserID string `json:"user_id"` Email string `json:"email"` AccountStatus string `json:"account_status"` RateLimit struct { Limit int `json:"limit"` Remaining int `json:"remaining"` ResetAt string `json:"reset_at"` } `json:"rate_limit"` Permissions []string `json:"permissions"` } `json:"data"` } func testAPIKey(apiKey string) error { req, err := http.NewRequest("GET", "https://api.hitl.sh/v1/test", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+apiKey) client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() var result TestResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return err } if !result.Error { fmt.Println("✅ API key is valid!") fmt.Printf("Account: %s\n", result.Data.Email) fmt.Printf("Status: %s\n", result.Data.AccountStatus) fmt.Printf("Rate limit: %d/%d\n", result.Data.RateLimit.Remaining, result.Data.RateLimit.Limit) } return nil } func main() { err := testAPIKey("your_api_key_here") if err != nil { fmt.Printf("❌ API key validation failed: %v\n", err) } } ``` ```json 200 Success theme={null} { "error": false, "msg": "API key is valid", "data": { "api_key_id": "65f1234567890abcdef12349", "user_id": "65f1234567890abcdef12346", "email": "user@example.com", "account_status": "active", "rate_limit": { "limit": 100, "remaining": 95, "reset_at": "2024-03-15T15:00:00Z" }, "permissions": [ "loops:read", "loops:write", "requests:read", "requests:write" ] } } ``` ```json 401 Invalid API Key theme={null} { "error": true, "msg": "Invalid API key" } ``` ```json 429 Rate Limited theme={null} { "error": true, "msg": "API rate limit exceeded", "data": { "usage_count": 100, "usage_limit": 100, "remaining": 0 } } ``` ## Use Cases ### Integration Testing Use this endpoint in your CI/CD pipeline to verify API key setup: ```python theme={null} def test_api_key_setup(): """Test that API key is properly configured""" response = requests.get( "https://api.hitl.sh/v1/test", headers={"Authorization": f"Bearer {os.environ['HITL_API_KEY']}"} ) assert response.status_code == 200 data = response.json() assert data["error"] == False assert data["data"]["account_status"] == "active" print("✅ API key configuration verified") ``` ### Rate Limit Monitoring Check your rate limit status before making batch requests: ```javascript theme={null} async function checkRateLimits() { const response = await axios.get('https://api.hitl.sh/v1/test', { headers }); const rateLimit = response.data.data.rate_limit; if (rateLimit.remaining < 10) { console.warn(`Low rate limit: ${rateLimit.remaining}/${rateLimit.limit} remaining`); console.log(`Resets at: ${rateLimit.reset_at}`); } return rateLimit; } ``` ### Health Check Endpoint Use in your application's health checks: ```python theme={null} def health_check(): """Application health check including HITL.sh API""" try: response = requests.get( "https://api.hitl.sh/v1/test", headers={"Authorization": f"Bearer {HITL_API_KEY}"}, timeout=5 ) if response.status_code == 200 and not response.json()["error"]: return {"hitl_api": "healthy"} else: return {"hitl_api": "unhealthy", "error": response.json()} except Exception as e: return {"hitl_api": "unhealthy", "error": str(e)} ``` ### Debugging Authentication Issues When experiencing authentication problems, this endpoint provides detailed information: ```python theme={null} def debug_auth_issues(): """Debug authentication problems""" try: response = requests.get("https://api.hitl.sh/v1/test", headers=headers) if response.status_code == 200: data = response.json()["data"] print("🔍 Authentication Debug Info:") print(f" API Key ID: {data['api_key_id']}") print(f" Account Status: {data['account_status']}") print(f" Permissions: {', '.join(data['permissions'])}") print(f" Rate Limit: {data['rate_limit']['remaining']}/{data['rate_limit']['limit']}") elif response.status_code == 401: print("❌ Invalid API key - check your authorization header") elif response.status_code == 429: print("⏰ Rate limit exceeded - wait before retrying") except requests.exceptions.RequestException as e: print(f"🌐 Network error: {e}") ``` ## Best Practices * Call this endpoint periodically in production to monitor API key health * Set up alerts if the endpoint returns unexpected responses * Include it in your application's readiness probes * Check rate limits before making large batch operations * Implement backoff strategies when limits are low * Monitor the `reset_at` timestamp for planning * Test API keys in each environment (dev, staging, prod) * Verify permissions match expected capabilities * Ensure account status is active before deployment * Handle all possible response codes (200, 401, 429) * Log authentication failures for debugging * Implement retry logic with exponential backoff ## Common Issues If this endpoint fails, check these common issues: 1. **Missing Authorization header** - Ensure you're including `Authorization: Bearer your_api_key` 2. **Incorrect header format** - Don't include extra spaces or use wrong prefixes 3. **Revoked API key** - Check your dashboard if the key was revoked 4. **Network issues** - Verify you can reach `api.hitl.sh` 5. **Rate limits** - Wait if you've exceeded your hourly limit ## Next Steps After verifying your API key, create a loop to start processing requests. Learn more about API key management and security best practices. Learn how to handle and debug API errors effectively. # Webhooks Source: https://docs.hitl.sh/api-reference/webhooks Receive real-time notifications when requests are completed using callback URLs. Configure callbacks on a per-request basis for instant updates. # Webhooks Instead of polling the API to check request status, you can provide a `callback_url` when creating a request. HITL.sh will send an HTTP POST to your callback URL when the request is completed, timed out, or cancelled. ## How Callback URLs Work When creating a request, include the `callback_url` parameter: ```python theme={null} import requests request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user comment", "response_type": "single_select", "response_config": { "options": ["Approve", "Reject"] }, "default_response": "Reject", "timeout_seconds": 3600, "callback_url": "https://your-app.com/webhook/hitl/completed", "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ## Webhook Payload When a request is completed, HITL.sh sends a POST request to your `callback_url` with the following JSON payload: ```json theme={null} { "event": "request.completed", "request_id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "status": "completed", "response_data": { "selected_value": "Approve" }, "response_by": { "user_id": "65f1234567890abcdef12346", "name": "John Doe", "email": "john@example.com" }, "response_at": "2024-03-15T10:45:00Z", "response_time_seconds": 245.5, "created_at": "2024-03-15T10:41:00Z" } ``` ### Payload Fields Event type - always `"request.completed"`, `"request.timeout"`, or `"request.cancelled"` Unique identifier for the request ID of the loop that processed this request Final status: `"completed"`, `"timeout"`, or `"cancelled"` The actual response from the reviewer (format varies by response\_type). Null if timed out or cancelled. Information about the reviewer who responded. Null if timed out or cancelled. ISO 8601 timestamp when the response was submitted. Null if timed out or cancelled. Time taken from creation to response in seconds. Null if timed out or cancelled. ISO 8601 timestamp when the request was created ## Event Types Sent when a reviewer successfully completes the request. ```json theme={null} { "event": "request.completed", "status": "completed", "response_data": { /* reviewer's response */ }, "response_by": { /* reviewer info */ }, "response_at": "2024-03-15T10:45:00Z" } ``` Sent when no reviewer responds within the timeout period. ```json theme={null} { "event": "request.timeout", "status": "timeout", "response_data": null, "response_by": null, "response_at": null } ``` Your application should use the `default_response` value you specified when creating the request. Sent when the request is cancelled via the API before completion. ```json theme={null} { "event": "request.cancelled", "status": "cancelled", "response_data": null, "response_by": null, "response_at": null } ``` ## Implementing a Webhook Endpoint ### Basic Endpoint ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import hmac import hashlib app = Flask(__name__) @app.route('/webhook/hitl/completed', methods=['POST']) def hitl_webhook(): # Get the webhook payload payload = request.get_json() request_id = payload['request_id'] event = payload['event'] status = payload['status'] if event == 'request.completed': # Handle completed request response_data = payload['response_data'] reviewer = payload['response_by'] print(f"Request {request_id} completed by {reviewer['name']}") print(f"Response: {response_data}") # Update your database, trigger workflows, etc. process_completed_request(request_id, response_data) elif event == 'request.timeout': # Handle timeout print(f"Request {request_id} timed out") use_default_response(request_id) elif event == 'request.cancelled': # Handle cancellation print(f"Request {request_id} was cancelled") mark_request_cancelled(request_id) # Return 200 OK to acknowledge receipt return jsonify({"status": "received"}), 200 if __name__ == '__main__': app.run(port=5000) ``` ```javascript Node.js (Express) theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhook/hitl/completed', (req, res) => { const payload = req.body; const requestId = payload.request_id; const event = payload.event; const status = payload.status; if (event === 'request.completed') { // Handle completed request const responseData = payload.response_data; const reviewer = payload.response_by; console.log(`Request ${requestId} completed by ${reviewer.name}`); console.log(`Response:`, responseData); // Update your database, trigger workflows, etc. processCompletedRequest(requestId, responseData); } else if (event === 'request.timeout') { // Handle timeout console.log(`Request ${requestId} timed out`); useDefaultResponse(requestId); } else if (event === 'request.cancelled') { // Handle cancellation console.log(`Request ${requestId} was cancelled`); markRequestCancelled(requestId); } // Return 200 OK to acknowledge receipt res.status(200).json({ status: 'received' }); }); app.listen(5000, () => { console.log('Webhook server running on port 5000'); }); ``` ## Best Practices ### 1. Return 200 OK Quickly Always return a `200 OK` response immediately after receiving the webhook. Process the payload asynchronously to avoid timeouts: ```python theme={null} @app.route('/webhook/hitl/completed', methods=['POST']) def hitl_webhook(): payload = request.get_json() # Queue the payload for async processing task_queue.enqueue(process_webhook, payload) # Return immediately return jsonify({"status": "received"}), 200 ``` ### 2. Handle Retries Gracefully HITL.sh will retry failed webhook deliveries up to 3 times with exponential backoff. Make your endpoint idempotent to handle duplicate deliveries: ```python theme={null} def process_webhook(payload): request_id = payload['request_id'] # Check if already processed if is_already_processed(request_id): print(f"Webhook for {request_id} already processed, skipping") return # Process the webhook handle_request_completion(payload) # Mark as processed mark_as_processed(request_id) ``` ### 3. Validate Webhook Authenticity While HITL.sh callback URLs are set per-request and only known to you, you should still validate incoming webhooks: ```python theme={null} def validate_webhook_source(request): # Check source IP (optional) allowed_ips = ['52.25.180.123', '54.245.23.45'] # HITL.sh IPs source_ip = request.remote_addr if source_ip not in allowed_ips: return False # Verify required fields are present payload = request.get_json() required_fields = ['event', 'request_id', 'status'] return all(field in payload for field in required_fields) ``` ### 4. Use HTTPS Always use HTTPS endpoints for your `callback_url` to ensure webhook payloads are encrypted in transit: ```python theme={null} # Good callback_url = "https://your-app.com/webhook/hitl" # Bad - don't use HTTP callback_url = "http://your-app.com/webhook/hitl" # ❌ Insecure ``` ### 5. Handle Errors Gracefully If your webhook endpoint fails, HITL.sh will retry. Log errors for debugging: ```python theme={null} @app.route('/webhook/hitl/completed', methods=['POST']) def hitl_webhook(): try: payload = request.get_json() process_webhook(payload) return jsonify({"status": "received"}), 200 except Exception as e: logger.error(f"Webhook processing failed: {str(e)}", exc_info=True) # Return 500 to trigger retry return jsonify({"error": str(e)}), 500 ``` ## Testing Webhooks ### Local Development with ngrok Use ngrok to expose your local server for webhook testing: ```bash theme={null} # Start your local server python app.py # Running on localhost:5000 # In another terminal, start ngrok ngrok http 5000 # Use the ngrok URL as your callback_url # Example: https://abc123.ngrok.io/webhook/hitl/completed ``` ### Manual Testing Create a test request with your callback URL: ```python theme={null} import requests test_request = { "processing_type": "time-sensitive", "type": "markdown", "priority": "low", "request_text": "Test webhook - please select any option", "response_type": "single_select", "response_config": { "options": ["Option A", "Option B"] }, "default_response": "Option A", "timeout_seconds": 300, # 5 minutes "callback_url": "https://your-ngrok-url.ngrok.io/webhook/hitl/completed", "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=test_request ) print(f"Test request created: {response.json()['data']['request_id']}") print("Respond to it in the HITL mobile app to trigger the webhook") ``` ## Troubleshooting **Possible causes:** * Callback URL is not publicly accessible * Using HTTP instead of HTTPS * Firewall blocking incoming requests * Webhook endpoint returned error status **Solutions:** * Test your endpoint with curl or Postman * Ensure HTTPS is used * Check firewall rules * Return 200 OK status code **Cause:** Webhook delivery retries after temporary failures **Solution:** Implement idempotency by tracking processed request IDs **Cause:** Your endpoint takes too long to respond **Solution:** Return 200 OK immediately and process payload asynchronously ## Next Steps Learn how to create requests with callback URLs Understand request lifecycle and polling alternatives See complete examples with webhook integration Handle webhook delivery failures and retries # Integrations Source: https://docs.hitl.sh/concepts/integrations Learn how HITL.sh integrates with your existing systems, tools, and platforms to create seamless human-in-the-loop workflows # Integrations HITL.sh is designed to integrate seamlessly with your existing systems and workflows. Whether you're using popular automation platforms, custom applications, or enterprise systems, HITL.sh provides multiple integration methods to fit your needs. ## Integration Methods ### REST API Integration The most flexible integration method for custom applications: * **Full Control**: Complete control over request creation and response handling * **Custom Logic**: Implement your own business logic and error handling * **Real-time Processing**: Immediate request submission and response retrieval * **Scalability**: Handle high-volume workflows with custom queuing ### Webhook Integration Receive real-time notifications when human decisions are made: * **Real-time Updates**: Instant notifications when responses are ready * **Event-driven**: Trigger actions based on specific events * **Reliable Delivery**: Automatic retry and error handling * **Easy Setup**: Simple endpoint configuration ### Platform Integrations Connect with popular automation and workflow platforms: Automate workflows with visual automation builder. Create complex automation scenarios with visual tools. Connect HITL.sh with 5000+ apps and services. Integrate with Model Context Protocol for AI assistants. ## REST API Integration ### Authentication Secure your API calls with API keys: ```python theme={null} import requests class HITLClient: def __init__(self, api_key, base_url="https://api.hitl.sh/v1"): self.api_key = api_key self.base_url = base_url self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } def create_request(self, loop_id, data): response = requests.post( f"{self.base_url}/loops/{loop_id}/requests", headers=self.headers, json=data ) return response.json() def get_response(self, request_id): response = requests.get( f"{self.base_url}/requests/{request_id}/response", headers=self.headers ) return response.json() ``` ### Request Creation Submit requests for human review: ```python theme={null} def submit_content_for_review(content, loop_id): client = HITLClient(API_KEY) request_data = { "content": content.text, "content_type": "text", "priority": "normal", "ai_analysis": { "confidence": content.ai_confidence, "flags": content.ai_flags, "risk_score": content.risk_score }, "metadata": { "user_id": content.user_id, "timestamp": content.created_at.isoformat(), "source": "content_api" } } try: response = client.create_request(loop_id, request_data) return response["id"] except Exception as e: logger.error(f"Failed to submit request: {e}") raise ``` ### Response Handling Process human decisions when they're ready: ```python theme={null} def process_human_decision(request_id): client = HITLClient(API_KEY) # Poll for response (in production, use webhooks instead) while True: response = client.get_response(request_id) if response and response.get("status") == "completed": decision = response["decision"] if decision == "approved": handle_approval(request_id, response) elif decision == "rejected": handle_rejection(request_id, response) elif decision == "needs_changes": handle_modification_request(request_id, response) break time.sleep(30) # Wait 30 seconds before checking again ``` ## Webhook Integration ### Webhook Configuration Set up webhooks to receive real-time notifications: ```python theme={null} def configure_webhook(loop_id, webhook_url): client = HITLClient(API_KEY) webhook_data = { "url": webhook_url, "events": ["request.completed", "request.escalated"], "loop_id": loop_id, "secret": generate_webhook_secret() } response = client.create_webhook(webhook_data) return response["id"] ``` ### Webhook Handler Process incoming webhook notifications: ```python theme={null} from flask import Flask, request, jsonify import hmac import hashlib app = Flask(__name__) WEBHOOK_SECRET = "your_webhook_secret" @app.route('/webhooks/hitl', methods=['POST']) def handle_hitl_webhook(): # Verify webhook signature signature = request.headers.get('X-HITL-Signature') if not verify_signature(request.data, signature): return jsonify({"error": "Invalid signature"}), 401 payload = request.json event_type = payload["event"] if event_type == "request.completed": process_completed_request(payload["data"]) elif event_type == "request.escalated": handle_escalation(payload["data"]) return jsonify({"status": "success"}), 200 def verify_signature(payload, signature): expected_signature = hmac.new( WEBHOOK_SECRET.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected_signature, signature) def process_completed_request(data): request_id = data["request_id"] decision = data["decision"] # Process the human decision if decision == "approved": approve_content(request_id) elif decision == "rejected": reject_content(request_id) ``` ## Platform-Specific Integrations ### N8N Integration Automate workflows with N8N's visual automation builder: N8N workflow showing HITL.sh integration for content moderation **Key Features:** * **Visual Workflow Builder**: Drag-and-drop interface for workflow creation * **Trigger Nodes**: Start workflows based on HITL.sh events * **Action Nodes**: Submit requests and process responses * **Error Handling**: Built-in retry logic and error management * **Scheduling**: Time-based workflow execution **Use Cases:** * Content moderation workflows * Customer support escalations * Quality assurance processes * Compliance verification ### Zapier Integration Connect HITL.sh with thousands of apps and services: * **New Request Created**: When a request is submitted for review * **Request Completed**: When human decision is received * **Request Escalated**: When escalation occurs * **Create Request**: Submit content for human review * **Update Request**: Modify existing request details * **Get Response**: Retrieve human decision data **Popular Integrations:** * **Slack**: Notify teams about pending reviews * **Gmail**: Send review requests via email * **Google Sheets**: Log decisions and track metrics * **Notion**: Document review processes and decisions ### Make Integration Build complex automation scenarios with Make: * **Scenario Builder**: Visual workflow creation with advanced logic * **Data Mapping**: Transform data between different formats * **Conditional Logic**: Route requests based on content characteristics * **Error Handling**: Comprehensive error management and recovery **Advanced Workflows:** * Multi-step approval processes * Conditional routing based on content type * Integration with multiple data sources * Complex escalation chains ### MCP Server Integration Connect HITL.sh with AI assistants using Model Context Protocol: * **AI Assistant Integration**: Connect Claude, GPT, and other AI assistants * **Human-in-the-Loop Workflows**: Add human oversight to AI agent workflows * **Context Management**: Share context between AI and human reviewers * **Seamless Handoff**: Smooth transition from AI to human decision-making **Use Cases:** * AI agents requesting human approval for critical decisions * Quality control for AI-generated content * Human verification of automated actions * Escalation from AI to human experts **Installation:** ```bash theme={null} npm install @hitl/mcp-server ``` **Configuration:** ```json theme={null} { "mcpServers": { "hitl": { "command": "npx", "args": ["-y", "@hitl/mcp-server"], "env": { "HITL_API_KEY": "your_api_key_here" } } } } ``` ## Custom Application Integration ### Frontend Integration Integrate HITL.sh into your web applications: ```javascript theme={null} class HITLIntegration { constructor(apiKey, baseUrl) { this.apiKey = apiKey; this.baseUrl = baseUrl; } async submitForReview(content, loopId) { const response = await fetch(`${this.baseUrl}/loops/${loopId}/requests`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content: content.text, content_type: 'text', priority: 'normal' }) }); return response.json(); } async checkStatus(requestId) { const response = await fetch(`${this.baseUrl}/requests/${requestId}/status`, { headers: { 'Authorization': `Bearer ${this.apiKey}` } }); return response.json(); } } // Usage in your application const hitl = new HITLIntegration(API_KEY, 'https://api.hitl.sh/v1'); document.getElementById('submit-button').addEventListener('click', async () => { const content = document.getElementById('content-input').value; const request = await hitl.submitForReview(content, 'loop_123'); // Show pending status showPendingStatus(request.id); // Poll for completion pollForCompletion(request.id); }); ``` ### Backend Integration Integrate with your server-side applications: ```python theme={null} # Django integration example from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt import json class HITLService: def __init__(self, api_key): self.client = HITLClient(api_key) def submit_content_review(self, content, user_id): """Submit content for human review""" request_data = { "content": content, "content_type": "text", "priority": "normal", "metadata": { "user_id": str(user_id), "timestamp": timezone.now().isoformat() } } response = self.client.create_request("content_moderation", request_data) return response["id"] def handle_webhook(self, payload): """Process webhook notifications""" event_type = payload["event"] if event_type == "request.completed": self.process_completed_request(payload["data"]) elif event_type == "request.escalated": self.handle_escalation(payload["data"]) # Django view for webhook handling @csrf_exempt def hitl_webhook(request): if request.method == 'POST': payload = json.loads(request.body) hitl_service = HITLService(settings.HITL_API_KEY) hitl_service.handle_webhook(payload) return JsonResponse({"status": "success"}) return JsonResponse({"error": "Method not allowed"}, status=405) ``` ## Integration Best Practices ### Security Store API keys securely in environment variables or secure vaults. Always verify webhook signatures to prevent unauthorized access. Implement rate limiting to avoid overwhelming the API. Handle API errors gracefully with retry logic and fallbacks. ### Performance Use webhooks instead of polling for better performance. Group multiple requests when possible to reduce API calls. Cache frequently accessed data to minimize API requests. Reuse HTTP connections for better performance. ### Monitoring * **API Usage**: Track API call volumes and response times * **Error Rates**: Monitor failed requests and error patterns * **Webhook Delivery**: Ensure webhook notifications are received * **Performance Metrics**: Track integration performance and bottlenecks ## Next Steps Ready to integrate HITL.sh with your systems? Learn about specific platform integrations like N8N and Zapier. Configure webhooks for real-time notifications. Detailed API documentation for custom integrations. # Loops Source: https://docs.hitl.sh/concepts/loops Learn about loops - the core organizational unit in HITL.sh that connects reviewers with requests requiring human oversight # Loops Loops are the foundational organizational structure in HITL.sh that connect human reviewers with content and decisions requiring oversight. Think of a loop as a team or group of reviewers who collaborate to handle specific types of requests from your applications. ## What is a Loop? A loop in HITL.sh is a structured group that: * **Manages Reviewers**: Organizes human reviewers who can respond to requests * **Receives Requests**: Accepts requests created via the API for human review * **Broadcasts Notifications**: Sends push notifications to mobile devices when new requests arrive * **Tracks Member Activity**: Monitors who's active and available to respond * **Controls Access**: Manages who can create requests and who can review them Loops are created and managed by API key holders, while reviewers join loops via the mobile app using invite codes or QR codes. ## Loop Components ### Basic Properties Every loop has fundamental properties that define its identity and behavior: * **Name**: Clear, descriptive identifier for the loop (required, max 100 characters) * **Description**: Detailed explanation of the loop's purpose and scope (optional, max 500 characters) * **Icon**: Visual identifier for the loop (required, max 100 characters - typically an emoji or short text) * **Creator**: User who created and owns the loop * **Active Members**: Reviewers who can receive and respond to requests * **Pending Members**: Users who've been invited but haven't joined yet * **Member Count**: Total number of people in the loop * **Invite Code**: Unique code for joining the loop * **Creator Permissions**: Only loop creators can modify settings and add requests * **Member Permissions**: Members can only view and respond to requests * **API Access**: Only the creator's API key can create requests in the loop ### Invite System Loops use a simple but effective invite system: ```python theme={null} # Create a loop and get invite details import requests loop_data = { "name": "Content Moderation Team", "description": "Reviews user-generated content for community guidelines compliance", "icon": "🛡️" } response = requests.post( "https://api.hitl.sh/v1/api/loops", headers={"Authorization": f"Bearer {api_key}"}, json=loop_data ) loop_response = response.json()["data"] loop = loop_response["loop"] print(f"Loop created: {loop['name']}") print(f"Invite code: {loop_response['invite_code']}") print(f"QR code URL: {loop_response['qr_code_url']}") print(f"Join URL: {loop_response['join_url']}") ``` Reviewers can join using: * **QR Code**: Scan with the HITL mobile app camera * **Invite Code**: Enter the 6-digit code in the app * **Direct Link**: Share URL that opens the mobile app directly ## Loop Lifecycle ### 1. Creation Loops are created via the API by users who need human reviewers: ```python Python theme={null} import requests # Create a new loop loop_data = { "name": "Financial Transaction Review", "description": "Human oversight for suspicious financial transactions", "icon": "💰" } response = requests.post( "https://api.hitl.sh/v1/api/loops", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json=loop_data ) if response.status_code == 201: loop = response.json()["data"]["loop"] print(f"✅ Loop created: {loop['id']}") print(f"📱 Share invite code: {loop['invite_code']}") else: print("❌ Failed to create loop:", response.json()) ``` ```javascript Node.js theme={null} const axios = require('axios'); async function createLoop() { const loopData = { name: "Product Review Team", description: "Reviews product listings and descriptions for accuracy", icon: "📦" }; try { const response = await axios.post( "https://api.hitl.sh/v1/api/loops", loopData, { headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" } } ); const loop = response.data.data.loop; console.log(`✅ Loop created: ${loop.name}`); console.log(`📱 Invite code: ${loop.invite_code}`); return loop; } catch (error) { console.error("❌ Failed to create loop:", error.response?.data); } } ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support Escalation", "description": "Human agents review escalated customer support tickets", "icon": "🎧" }' ``` ### 2. Reviewer Recruitment Once created, loops need human reviewers to function: Distribute the 6-digit invite code to potential reviewers via email, Slack, or other communication channels. Use the provided QR code URL to generate scannable codes for easy mobile app joining. Create deep links that open the HITL mobile app directly to the join screen. Track who joins and ensure you have adequate reviewer coverage. ### 3. Active Operation With reviewers in place, loops continuously process requests: ```python theme={null} # Monitor loop activity response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers={"Authorization": f"Bearer {api_key}"} ) loop = response.json()["data"]["loop"] print(f"Loop: {loop['name']}") print(f"Active members: {loop['member_count'] - loop['pending_count']}") print(f"Pending invites: {loop['pending_count']}") # Get recent requests for this loop requests_response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"} ) requests_data = requests_response.json()["data"]["requests"] print(f"Total requests: {len(requests_data)}") # Count by status pending = len([r for r in requests_data if r["status"] == "pending"]) completed = len([r for r in requests_data if r["status"] == "completed"]) print(f"Pending: {pending}, Completed: {completed}") ``` ### 4. Management & Updates Loop creators can modify settings and membership: ```python Python theme={null} # Update loop information update_data = { "name": "Enhanced Content Moderation", "description": "Advanced review team with updated guidelines and training", "icon": "🔍" } response = requests.put( f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers={"Authorization": f"Bearer {api_key}"}, json=update_data ) if response.status_code == 200: print("✅ Loop updated successfully") else: print("❌ Update failed:", response.json()) ``` ```python Python theme={null} # Remove inactive members member_id = "65f1234567890abcdef12346" response = requests.delete( f"https://api.hitl.sh/v1/api/loops/{loop_id}/members/{member_id}", headers={"Authorization": f"Bearer {api_key}"} ) if response.status_code == 200: print("✅ Member removed successfully") else: print("❌ Removal failed:", response.json()) ``` ## Loop Types & Use Cases ### Content Moderation Loops Review user-generated content for policy compliance: ```python theme={null} moderation_loop = { "name": "Community Content Review", "description": "Reviews posts, comments, and user uploads for community guideline violations", "icon": "🛡️" } # Typical requests for this loop type request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": f"Review this comment: '{user_comment}'", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "warn", "label": "⚠️ Warn User", "color": "#f59e0b"} ], "required": True }, "default_response": "reject", "timeout_seconds": 1800, # 30 minutes "platform": "api" } ``` ### Quality Assurance Loops Review AI-generated or automated content: ```python theme={null} qa_loop = { "name": "AI Content Quality Review", "description": "Human experts review AI-generated content for accuracy and quality", "icon": "🎯" } # Quality rating request request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": f"Rate the quality of this AI-generated article:\n\n{article_content}", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5, "labels": { "1": "Poor - Needs complete rewrite", "5": "Average - Acceptable with minor edits", "10": "Excellent - Publish as-is" }, "required": True }, "default_response": 5, "timeout_seconds": 86400, # 24 hours "platform": "api" } ``` ### Business Process Loops Handle approval workflows and decision-making: ```python theme={null} approval_loop = { "name": "Expense Approval Team", "description": "Reviews and approves employee expense reports and reimbursements", "icon": "💼" } # Approval decision request request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": f"Approve this expense report:\n\nAmount: ${amount}\nCategory: {category}\nDescription: {description}", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve Expense"}, {"value": "reject", "label": "❌ Reject Expense"} ], "required": True }, "default_response": "reject", # Conservative default "timeout_seconds": 14400, # 4 hours "platform": "api" } ``` ### Verification & Compliance Loops Verify information accuracy and regulatory compliance: ```python theme={null} compliance_loop = { "name": "Data Verification Team", "description": "Verifies business information and ensures regulatory compliance", "icon": "📋" } # Multi-select verification request request_data = { "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": f"Verify this business listing:\n\n{business_info}", "response_type": "multi_select", "response_config": { "options": [ {"value": "address_verified", "label": "📍 Address Verified"}, {"value": "phone_verified", "label": "📞 Phone Verified"}, {"value": "hours_verified", "label": "🕒 Hours Verified"}, {"value": "website_verified", "label": "🌐 Website Verified"}, {"value": "license_verified", "label": "📜 License Verified"} ], "min_selections": 1, "max_selections": 5, "required": True }, "default_response": "", "timeout_seconds": 259200, # 3 days "platform": "api" } ``` ## Loop Management ### Monitoring Loop Health Keep track of loop performance and member engagement: ```python theme={null} def analyze_loop_performance(loop_id): # Get loop details loop_response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}", headers={"Authorization": f"Bearer {api_key}"} ) loop = loop_response.json()["data"]["loop"] # Get recent requests requests_response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"} ) requests_data = requests_response.json()["data"]["requests"] # Analyze metrics total_requests = len(requests_data) completed_requests = [r for r in requests_data if r["status"] == "completed"] timeout_requests = [r for r in requests_data if r["status"] == "timeout"] completion_rate = len(completed_requests) / total_requests if total_requests > 0 else 0 timeout_rate = len(timeout_requests) / total_requests if total_requests > 0 else 0 print(f"Loop: {loop['name']}") print(f"Members: {loop['member_count']} ({loop['member_count'] - loop['pending_count']} active)") print(f"Requests: {total_requests} total, {len(completed_requests)} completed") print(f"Completion rate: {completion_rate:.1%}") print(f"Timeout rate: {timeout_rate:.1%}") # Performance insights if completion_rate < 0.8: print("⚠️ Low completion rate - consider adding more reviewers") if timeout_rate > 0.2: print("⚠️ High timeout rate - consider adjusting timeouts or priorities") if loop['member_count'] - loop['pending_count'] < 3: print("⚠️ Few active members - recruit more reviewers for reliability") # Monitor your loops analyze_loop_performance(loop_id) ``` ### Member Management Track and manage loop membership: ```python theme={null} # Get detailed member information members_response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}/members", headers={"Authorization": f"Bearer {api_key}"} ) members = members_response.json()["data"]["members"] print("Loop Members:") for member in members: status_emoji = "✅" if member["status"] == "active" else "⏳" joined_date = member.get("joined_at", "Unknown") print(f" {status_emoji} {member['name']} ({member['email']}) - Joined: {joined_date}") # Remove inactive members if needed inactive_members = [m for m in members if m["status"] == "pending" and is_old_invite(m)] for member in inactive_members: print(f"Removing inactive member: {member['email']}") requests.delete( f"https://api.hitl.sh/v1/api/loops/{loop_id}/members/{member['user_id']}", headers={"Authorization": f"Bearer {api_key}"} ) ``` ## Best Practices ### Loop Design Give loops specific, well-defined purposes rather than general "review everything" mandates. Aim for 3-10 active members per loop - enough for coverage but small enough for accountability. Use names that clearly indicate what the loop reviews and how urgent it is. Periodically review membership, remove inactive users, and update descriptions. ### Operational Efficiency Always have multiple active reviewers to ensure coverage during off-hours or vacations. Use different loops for different priority levels rather than mixing urgent and routine requests. Create specialized loops for different types of content (text, images, different domains). Regularly check completion rates and response times to identify bottlenecks. ### Reviewer Experience * **Clear Guidelines**: Provide reviewers with explicit instructions and examples * **Reasonable Workload**: Don't overwhelm loops with too many requests * **Feedback Loop**: Use the feedback API to acknowledge good reviewer performance * **Training Materials**: Share documentation about what to look for and how to respond ## Scaling Patterns ### Single Loop → Multiple Loops As your needs grow, consider splitting loops: ```python theme={null} # Start with one general loop general_loop = create_loop("Content Review", "Reviews all user content") # Split into specialized loops as volume increases text_loop = create_loop("Text Content Review", "Reviews posts, comments, and messages") image_loop = create_loop("Image Content Review", "Reviews uploaded photos and graphics") urgent_loop = create_loop("Urgent Review Team", "High-priority content requiring immediate attention") ``` ### Geographic Distribution Create region-specific loops for timezone coverage: ```python theme={null} # Timezone-based loops for 24/7 coverage us_loop = create_loop("US Review Team", "Primary coverage: 9 AM - 5 PM EST") eu_loop = create_loop("EU Review Team", "Primary coverage: 9 AM - 5 PM CET") asia_loop = create_loop("Asia Review Team", "Primary coverage: 9 AM - 5 PM JST") # Route requests based on urgency and time def route_request_by_timezone(): current_hour_utc = datetime.utcnow().hour if 13 <= current_hour_utc <= 21: # US business hours return us_loop["id"] elif 8 <= current_hour_utc <= 16: # EU business hours return eu_loop["id"] else: # Asia business hours return asia_loop["id"] ``` ### Hierarchical Review Set up escalation between loops: ```python theme={null} # Primary review loop primary_loop = create_loop("First-Level Review", "Initial content screening") # Escalation loop for complex cases escalation_loop = create_loop("Senior Review Team", "Handles escalated and complex cases") # In your request handling: if reviewer_selected == "escalate": # Create new request in escalation loop escalated_request = create_request( loop_id=escalation_loop["id"], request_text=f"Escalated from primary review:\n\n{original_request}", # ... other configuration ) ``` ## Troubleshooting ### Common Issues **Problem**: Requests timeout because no reviewers are available. **Solutions**: * Check that invite codes have been shared and used * Verify reviewers have the mobile app installed and notifications enabled * Consider timezone differences when recruiting reviewers * Monitor member activity and remove/replace inactive users **Problem**: Many requests timeout instead of being completed. **Solutions**: * Increase timeout durations to allow more response time * Add more reviewers to increase coverage * Adjust priorities - too many high-priority requests reduce effectiveness * Check if request instructions are clear and actionable **Problem**: Reviewers give different answers to similar requests. **Solutions**: * Provide clearer guidelines and examples * Use more structured response types (single select vs. text) * Consider reviewer training or calibration exercises * Review response configurations to ensure options are clear **Problem**: Can't remove members or invite codes aren't working. **Solutions**: * **Only loop creators can manage membership** - including removing members, updating loop details, and deleting loops * Loop creators cannot remove themselves from the loop * Invite codes are case-sensitive and expire after extended periods * Removed members can rejoin with the same invite code if needed * Check API responses for specific error messages ## Next Steps Start building your reviewer team with the loops API Learn how to create and manage requests within your loops Help your reviewers get set up with the HITL mobile app Explore all available loop management endpoints # Requests Source: https://docs.hitl.sh/concepts/requests Understand how requests work in HITL.sh - from creation through human review to structured responses # Requests Requests are the core units of work that flow through HITL.sh loops. Each request represents content, data, or a decision that needs human review before proceeding. Requests are created via the API, broadcasted to human reviewers, and return structured responses. ## What is a Request? A request in HITL.sh contains: * **Content to Review**: The actual material requiring human oversight (text, images, data) * **Response Configuration**: How reviewers should respond (single select, rating, text, etc.) * **Processing Settings**: Timeout behavior, priority level, and platform tracking * **Loop Context**: Which loop handles the request and who receives notifications * **Timeout Handling**: What happens if no human responds within the time limit Requests are created by API consumers and reviewed by humans via the HITL mobile app. The structured responses are then available via API polling or webhooks. ## Request Lifecycle ### 1. Creation Requests are created when your application needs human oversight. The API validates the request configuration and immediately broadcasts it to active loop members. ```python theme={null} import requests # Create a content moderation request request_data = { "processing_type": "time-sensitive", # or "deferred" "type": "markdown", # markdown, image, file, video, or audio "priority": "high", # low, medium, high, critical "request_text": "Please review this user comment for guideline compliance: 'This product changed my life! Everyone should try it. Use my referral code SAVE20 for a discount.'", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "escalate", "label": "🚨 Escalate", "color": "#8b5cf6"} ], "required": True }, "default_response": "reject", # Safety default "timeout_seconds": 3600, # 1 hour "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) request_id = response.json()["data"]["request_id"] ``` ### 2. Broadcast Once created, requests are immediately broadcasted to all active members of the target loop: * **Push notifications** sent to mobile devices * **In-app notifications** appear in the HITL mobile app * **Email alerts** (if configured) for time-sensitive requests * **Request queuing** maintains order and prevents duplicate claims ### 3. Review & Response Reviewers interact with the configured response type in a mobile-optimized interface: * **Single select**: Tap one option from a visual list * **Multi select**: Check multiple options with validation * **Rating**: Drag a slider or tap star ratings * **Text**: Type detailed feedback with character count * **Number**: Enter numeric values with formatting ### 4. Completion When a reviewer submits their response: * **Validation**: Response is checked against configuration rules * **Storage**: Response data is stored with reviewer metadata * **Notification**: Original requester is notified (webhook/polling) * **Status update**: Request status changes to "completed" ## Request Types ### Processing Types **Immediate processing** with explicit timeout * Requires `timeout_seconds` parameter (60-86400 seconds / 1 minute to 24 hours) * Reviewers get urgent notifications * Used for content moderation, fraud detection * Typical timeouts: 900s (15 min) to 3600s (1 hour) **Non-urgent processing** with flexible timeline * Optional `timeout_seconds` (defaults to 30 days) * Lower priority in reviewer queues * Used for quality reviews, data verification * Typical timeouts: 1 day to 30 days ### Content Types **Text-based content** for review * Request text displayed as formatted markdown * Support for lists, links, code blocks * Mobile-optimized text rendering * Used for articles, comments, documents **Visual content** requiring review * Requires `image_url` parameter * Full-screen image viewing on mobile * Zoom and pan capabilities * Upload supported (10MB max) **File-based content** for review * Requires `file_url`, `file_type`, and `file_name` * Supports PDF, DOCX, and other document formats * Upload supported (50MB max) * Used for contracts, reports, compliance docs **Video content** requiring review * Requires `video_url` parameter * Supports YouTube, Vimeo, or raw .mp4/.webm/.mov * Link only — no upload supported * Used for training videos, recorded demos **Audio content** requiring review * Requires `audio_url` parameter * Supports SoundCloud, Spotify, or raw .mp3/.wav/.ogg/.m4a/.aac * Link only — no upload supported * Used for call recordings, podcasts, voice reviews ### Priority Levels Requests can be assigned priority levels that affect reviewer notification urgency and queue ordering: * Immediate push notifications * Red highlighting in mobile app * Appear at top of reviewer queues * Used for security threats, policy violations * Priority push notifications * Orange highlighting in mobile app * Elevated position in queues * Used for time-sensitive business decisions * Standard push notifications * Normal highlighting in mobile app * Default queue ordering * Used for routine content moderation * Minimal notifications * Subtle highlighting in mobile app * Lower position in queues * Used for quality improvements, feedback ## Response Types HITL.sh supports five response types, each with specific configuration options: ### Text Response Free-form text input with length validation: ```python theme={null} "response_type": "text", "response_config": { "placeholder": "Provide detailed feedback...", "min_length": 10, "max_length": 500, "required": True } ``` ### Single Select Response Choose one option from a predefined list: **Simple Format** (recommended for quick setup): ```python theme={null} "response_type": "single_select", "response_config": { "options": [ "Approve", "Reject", "Escalate" ] # required defaults to false } ``` **Rich Format** (for customization with colors and detailed labels): ```python theme={null} "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "escalate", "label": "🚨 Escalate", "color": "#8b5cf6"} ], "required": true } ``` Both formats are supported. Simple strings are automatically converted to rich option objects internally. Use the rich format when you need custom colors, icons, or want different display labels vs. stored values. ### Multi Select Response Choose multiple options with limits: **Simple Format**: ```python theme={null} "response_type": "multi_select", "response_config": { "options": [ "Issue Type A", "Issue Type B", "Issue Type C" ], "max_selections": 3 # min_selections defaults to 1 } ``` **Rich Format**: ```python theme={null} "response_type": "multi_select", "response_config": { "options": [ {"value": "type_a", "label": "Issue Type A", "color": "#3b82f6"}, {"value": "type_b", "label": "Issue Type B", "color": "#f59e0b"}, {"value": "type_c", "label": "Issue Type C", "color": "#ef4444"} ], "min_selections": 1, "max_selections": 3 } ``` ### Rating Response Numeric rating with custom scale: ```python theme={null} "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 0.5, "labels": { "1": "Poor", "3": "Average", "5": "Excellent" }, "required": True } ``` ### Number Response Numeric input with validation and formatting: ```python theme={null} "response_type": "number", "response_config": { "min_value": 0, "max_value": 1000 # decimal_places defaults to 2 (perfect for currency) # allow_negative defaults to false } ``` ## Request Status Flow Requests move through several states during their lifecycle: ```mermaid theme={null} graph TD A[Created] --> B[Pending] B --> C[Completed] B --> D[Timeout] B --> E[Cancelled] C --> F[Response Available] D --> F ``` * Request created and broadcasted to reviewers * Waiting for a reviewer to respond * Visible in all loop members' mobile apps * Can be cancelled by the creator * Reviewer has submitted their response * Response data is available via API * Request can no longer be modified * Feedback can be added by creator * No reviewer responded within the timeout period * Default response is used automatically * Request is marked as completed with timeout flag * Common for low-priority or off-hours requests * Creator cancelled the request before completion * No response data available * Can only cancel pending requests * Used when request is no longer needed ## Request Monitoring ### Polling for Status Check request status programmatically: ```python theme={null} # Get specific request response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": f"Bearer {api_key}"} ) request_data = response.json()["data"]["request"] status = request_data["status"] if status == "completed": response_data = request_data["response_data"] reviewer = request_data["response_by_user"] print(f"Request completed by {reviewer['name']}: {response_data}") elif status == "timeout": default_response = request_data["default_response"] print(f"Request timed out, using default: {default_response}") ``` ### Batch Monitoring Get all your requests: ```python theme={null} # Get all requests for this API key response = requests.get( "https://api.hitl.sh/v1/api/requests", headers={"Authorization": f"Bearer {api_key}"} ) requests_list = response.json()["data"]["requests"] # Filter by status pending_requests = [r for r in requests_list if r["status"] == "pending"] completed_requests = [r for r in requests_list if r["status"] == "completed"] print(f"You have {len(pending_requests)} pending requests") print(f"You have {len(completed_requests)} completed requests") ``` ## Advanced Features ### Context Data Include additional context that reviewers might need: ```python theme={null} request_data = { "request_text": "Review this user's profile update:", "context": { "user_id": "user_12345", "account_type": "premium", "previous_violations": 2, "country": "US", "registration_date": "2023-01-15" }, # ... other fields } ``` ### Callback URLs Get notified when requests complete: ```python theme={null} request_data = { "request_text": "Please review this content:", "callback_url": "https://your-app.com/webhook/hitl/completed", # ... other fields } ``` ### Platform Tracking Track which platform generated the request: ```python theme={null} request_data = { "request_text": "Review this automated flag:", "platform": "zapier", # api, n8n, zapier, webhook, web_portal, mobile "platform_version": "1.2.3", # Optional version tracking # ... other fields } ``` ## Error Handling ### Common Request Creation Errors ```json theme={null} { "error": true, "msg": "Invalid response configuration", "data": "options array required for select response type" } ``` **Solution**: Ensure response\_config matches the response\_type requirements. ```json theme={null} { "error": true, "msg": "Loop not found" } ``` **Solution**: Verify the loop ID exists and you have access to it. ```json theme={null} { "error": true, "msg": "No active members found in the loop" } ``` **Solution**: Ensure the loop has active members who can receive notifications. ```json theme={null} { "error": true, "msg": "timeout_seconds is required for time-sensitive requests" } ``` **Solution**: Add timeout\_seconds parameter (must be between 60 and 86400 seconds) for time-sensitive processing types. ### Request Access Errors ```json theme={null} { "error": true, "msg": "Access denied to this request" } ``` **Solution**: You can only access requests created with your API key. ```json theme={null} { "error": true, "msg": "Request cannot be cancelled in current state" } ``` **Solution**: Only pending requests can be cancelled. ## Best Practices ### Request Design Write request\_text that gives reviewers all the context they need to make informed decisions. Choose response types that match the complexity of the decision required. Set timeout\_seconds based on urgency and reviewer availability patterns. Always provide default\_response values that represent the safest outcome. ### Performance Optimization Create multiple requests in quick succession rather than waiting for each to complete. Use priority levels strategically - too many high-priority requests reduce their effectiveness. Monitor actual response times and adjust timeouts to balance urgency with completion rates. Distribute requests across multiple loops to prevent bottlenecks and ensure coverage. ### Quality Assurance * **Test configurations** with simple requests before deploying complex workflows * **Monitor completion rates** and adjust timeouts or priority levels accordingly * **Review default responses** to ensure they align with business requirements * **Track reviewer performance** to identify training needs or workload issues ## Next Steps Learn about all six response types and their configurations See the complete API reference for request creation Understand how reviewers interact with requests Learn how to set up and manage reviewer loops # Responses Source: https://docs.hitl.sh/concepts/responses Understand how human reviewers provide structured responses and how to configure and process them in your applications # Responses Responses are the structured decisions and feedback that human reviewers provide when completing HITL.sh requests. The response system supports five different types, each designed for specific use cases and providing different levels of structure and validation. ## What is a Response? A response in HITL.sh represents the human reviewer's decision and contains: * **Response Data**: The actual decision or feedback in a structured format * **Response Type**: Which of the six supported response types was used * **Reviewer Information**: Who provided the response and when * **Validation Status**: Whether the response meets the configured requirements * **Processing Metadata**: Response time, platform used, and other tracking data Responses are collected via the HITL mobile app and made available to your applications via API polling or webhooks. Each response type has its own data structure and validation rules. ## Response Types Overview HITL.sh supports five distinct response types, each optimized for different decision-making scenarios: **Free-form feedback** with character limits * Detailed explanations and qualitative feedback * Configurable length requirements * Perfect for open-ended reviews **One choice** from predefined options * Clear decision workflows * Visual options with colors and descriptions * Ideal for approve/reject scenarios **Multiple choices** from option lists * Issue identification and categorization * Configurable selection limits * Great for checklists and audits **Numeric ratings** on custom scales * Quality assessments and scoring * Custom labels and step increments * Perfect for performance evaluation **Numeric input** with validation * Pricing, quantities, measurements * Formatting with prefixes and suffixes * Range validation and decimal control ## Response Configuration When creating requests, you specify how reviewers should respond by setting the `response_type` and `response_config`: ### Text Responses For detailed feedback and explanations: ```python theme={null} "response_type": "text", "response_config": { "placeholder": "Explain your reasoning...", "min_length": 20, "max_length": 500, "required": True } ``` **Response Format:** ```json theme={null} { "response_data": "The content violates guideline 3.2 regarding promotional language. The phrase 'everyone should try it' is too promotional. Suggest rephrasing to 'this worked well for me' instead." } ``` ### Single Select Responses For structured decisions with predefined options: ```python theme={null} "response_type": "single_select", "response_config": { "options": [ "Approve", "Reject", "Escalate" ] # required defaults to false # Simple strings automatically converted to rich SelectOption objects } ``` **Response Format:** ```json theme={null} { "response_data": { "selected_value": "reject", "selected_label": "❌ Reject Content" } } ``` ### Multi Select Responses For identifying multiple issues or aspects: ```python theme={null} "response_type": "multi_select", "response_config": { "options": [ "Spam Content", "Inappropriate Language", "Misleading Claims" ], "max_selections": 3 # min_selections defaults to 1 # Simple strings automatically converted to rich SelectOption objects } ``` **Response Format:** ```json theme={null} { "response_data": { "selected_values": ["spam", "misleading"], "selected_labels": ["🚫 Spam Content", "❌ Misleading Claims"] } } ``` ### Rating Responses For quality assessments and scoring: ```python theme={null} "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5, "labels": { "1": "Poor Quality", "5": "Average", "10": "Excellent" }, "required": True } ``` **Response Format:** ```json theme={null} { "response_data": { "rating": 7.5, "rating_label": "Good Quality" } } ``` ### Number Responses For quantitative input with validation: ```python theme={null} "response_type": "number", "response_config": { "min_value": 0, "max_value": 1000 # decimal_places defaults to 2 (perfect for currency) # allow_negative defaults to false } ``` **Response Format:** ```json theme={null} { "response_data": { "number": 299.99, "formatted_value": "$299.99 USD" } } ``` ## Response Processing ### Accessing Response Data Once a reviewer completes a request, you can access the response data via API: ```python theme={null} import requests # Get completed request with response response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": f"Bearer {api_key}"} ) request_data = response.json()["data"]["request"] if request_data["status"] == "completed": # Extract response information response_data = request_data["response_data"] response_type = request_data["response_type"] reviewer = request_data["response_by_user"] response_time = request_data["response_time_seconds"] print(f"Response Type: {response_type}") print(f"Response Data: {response_data}") print(f"Completed by: {reviewer['name']} in {response_time:.1f}s") # Process based on response type process_response(response_type, response_data) ``` ### Response Processing Patterns Handle different response types appropriately in your application: ```python theme={null} def process_response(response_type, response_data): """Process responses based on their type""" if response_type == "text": # Process free-form feedback feedback = response_data save_feedback_for_review(feedback) elif response_type == "single_select": # Handle structured decision decision = response_data["selected_value"] if decision == "approve": approve_content() elif decision == "reject": reject_content() elif decision == "escalate": escalate_to_senior_team() elif response_type == "multi_select": # Handle multiple issues identified issues = response_data["selected_values"] for issue in issues: handle_content_issue(issue) elif response_type == "rating": # Handle quality score score = response_data["rating"] if score >= 8: mark_as_high_quality() elif score <= 3: flag_for_improvement() else: mark_as_acceptable() elif response_type == "number": # Handle numeric input value = response_data["number"] update_pricing_model(value) ``` ## Response Validation HITL.sh automatically validates responses against the configured rules: ### Validation Rules by Type * Response must be a non-empty string (if required) * Character count must be within min\_length and max\_length bounds * Cannot contain only whitespace if required * Selected values must exist in the configured options array * Single select allows exactly one selection * Multi select respects min\_selections and max\_selections limits * No duplicate selections allowed in multi select * Value must be within scale\_min and scale\_max bounds * Must align with scale\_step increments (e.g., only .0 and .5 for step=0.5) * Cannot be null if required * Value must be within min\_value and max\_value bounds * Decimal places cannot exceed configured limit * Negative numbers only allowed if allow\_negative is true ### Handling Validation Errors The mobile app prevents invalid responses, but you should handle edge cases: ```python theme={null} def validate_response_before_processing(request, response_data): """Additional validation before processing responses""" response_type = request["response_type"] response_config = request["response_config"] try: if response_type == "rating": rating = response_data["rating"] min_val = response_config["scale_min"] max_val = response_config["scale_max"] if not (min_val <= rating <= max_val): log_validation_error(f"Rating {rating} outside range [{min_val}, {max_val}]") return False elif response_type == "single_select": selected = response_data["selected_value"] valid_options = [opt["value"] for opt in response_config["options"]] if selected not in valid_options: log_validation_error(f"Invalid selection: {selected}") return False return True except KeyError as e: log_validation_error(f"Missing required field: {e}") return False ``` ## Response Analytics ### Tracking Response Patterns Monitor response patterns to improve your workflows: ```python theme={null} def analyze_response_patterns(loop_id, days=30): """Analyze response patterns for a loop""" # Get recent requests for this loop response = requests.get( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"} ) requests_data = response.json()["data"]["requests"] completed_requests = [r for r in requests_data if r["status"] == "completed"] # Analyze by response type response_type_counts = {} avg_response_times = {} for request in completed_requests: resp_type = request["response_type"] response_type_counts[resp_type] = response_type_counts.get(resp_type, 0) + 1 if request.get("response_time_seconds"): if resp_type not in avg_response_times: avg_response_times[resp_type] = [] avg_response_times[resp_type].append(request["response_time_seconds"]) # Calculate averages for resp_type, times in avg_response_times.items(): avg_time = sum(times) / len(times) print(f"{resp_type}: {response_type_counts[resp_type]} responses, avg {avg_time:.1f}s") ``` ### Response Quality Metrics Track response quality and consistency: ```python theme={null} def analyze_response_quality(requests_data): """Analyze quality metrics for responses""" # Group by response type by_type = {} for request in requests_data: if request["status"] != "completed": continue resp_type = request["response_type"] if resp_type not in by_type: by_type[resp_type] = [] by_type[resp_type].append(request) # Analyze each type for resp_type, requests in by_type.items(): print(f"\n{resp_type.upper()} Responses:") print(f" Total: {len(requests)}") # Response time analysis times = [r["response_time_seconds"] for r in requests if r.get("response_time_seconds")] if times: print(f" Avg response time: {sum(times)/len(times):.1f}s") print(f" Response time range: {min(times):.1f}s - {max(times):.1f}s") # Type-specific analysis if resp_type == "single_select": analyze_single_select_distribution(requests) elif resp_type == "rating": analyze_rating_distribution(requests) ``` ## Best Practices ### Choosing Response Types Use simple response types (single select) for straightforward decisions. Reserve complex types (multi select, text) for situations requiring nuanced evaluation. Remember that reviewers interact with responses on mobile devices. Keep options concise and touch-friendly. Structured responses (select, rating, number) are easier to process, but text responses provide richer feedback when needed. Consider how you'll process and analyze responses when choosing types. Structured responses are easier to aggregate and analyze. ### Response Design Tips For select responses, use descriptive labels and include helpful descriptions. Consider adding colors for visual clarity. Set appropriate character limits for text, selection limits for multi select, and ranges for numeric inputs. Always provide sensible default responses that represent safe outcomes when requests timeout. Use consistent terminology across response options to avoid confusion and improve decision quality. ## Next Steps Detailed documentation of all five response types with examples Learn how to configure responses when creating requests See how reviewers interact with different response types Learn how to integrate HITL.sh into your application with practical examples # Your Dashboard Source: https://docs.hitl.sh/dashboard Navigate and understand your HITL.sh dashboard to manage loops, requests, and team members effectively # Your Dashboard The HITL.sh dashboard is your central command center for managing all aspects of your human-in-the-loop workflows. From here, you can monitor requests, manage loops, configure integrations, and track performance metrics. ## Dashboard Overview HITL.sh dashboard showing main navigation and overview panels The dashboard is organized into several key areas: * **Navigation Sidebar**: Quick access to all major sections * **Overview Cards**: Real-time metrics and status information * **Recent Activity**: Latest requests and actions * **Quick Actions**: Common tasks and shortcuts ## Main Navigation ### Loops Management The **Loops** section is where you create and configure your human-in-the-loop workflows: View and manage all currently running loops in your system. Create reusable loop configurations for common workflows. ### Requests Overview Monitor all incoming and pending requests in real-time: * **Pending**: Requests waiting for human review * **In Progress**: Requests currently being reviewed * **Completed**: Successfully processed requests * **Failed**: Requests that encountered errors Requests are automatically categorized by status, making it easy to identify bottlenecks or issues in your workflow. ### Team Management Manage your human reviewers and their assignments: * **Reviewers**: Add, remove, and configure team members * **Skills**: Assign expertise areas to reviewers * **Schedules**: Set availability and response time expectations * **Performance**: Track response times and decision accuracy ## Key Metrics Your dashboard displays essential performance indicators: * **Average Response Time**: How quickly reviewers respond to requests * **95th Percentile**: Response time for 95% of requests * **Escalation Rate**: Percentage of requests that require escalation * **Decision Accuracy**: How often human decisions align with expected outcomes * **Inter-rater Reliability**: Consistency between different reviewers * **Feedback Quality**: Quality of human feedback for AI improvement * **Requests per Day**: Total number of requests processed * **Peak Load Times**: When your system experiences highest demand * **Loop Utilization**: How efficiently each loop is being used ## Quick Actions Access common tasks directly from your dashboard: Click the "Create Loop" button to set up a new human-in-the-loop workflow. Add team members to handle requests in your loops. Access detailed performance reports and insights. Set up connections to your existing systems and tools. ## Dashboard Customization Personalize your dashboard experience: * **Widget Layout**: Rearrange overview cards to match your priorities * **Notification Preferences**: Configure alerts for important events * **Default Views**: Set your preferred default dashboard view * **Theme Options**: Choose between light and dark themes Dashboard customizations are user-specific and won't affect other team members' views. ## Mobile Dashboard Access your dashboard on the go with our mobile-optimized interface: Download our mobile app for iOS and Android to manage your loops from anywhere. The mobile dashboard provides essential functionality while maintaining the same intuitive interface you're familiar with from the web version. ## Next Steps Now that you understand your dashboard, you're ready to: Configure authentication for your integrations. Build your first human-in-the-loop workflow. # Creating Your First Loop Source: https://docs.hitl.sh/first-loop Step-by-step guide to set up your first human-in-the-loop workflow with HITL.sh # Creating Your First Loop A loop in HITL.sh is a human-in-the-loop workflow that routes requests to human reviewers when AI systems need human oversight. This guide walks you through creating your first loop, adding reviewers, and processing your first request. ## What You'll Build In this tutorial, you'll create a **Content Moderation Loop** that: * Receives flagged content from your application * Automatically invites reviewers with QR codes and invite links * Routes requests to human reviewers via mobile notifications * Returns structured human decisions back to your system By the end of this guide, you'll have a fully functional loop with invite codes, QR codes, and the ability to process real requests. ## Prerequisites Before you begin, ensure you have: * ✅ A HITL.sh account (sign up at [my.hitl.sh](https://my.hitl.sh)) * ✅ The HITL.sh mobile app installed ([App Store](https://apps.apple.com/us/app/hitl-human-in-the-loop/id6752878072) | [Google Play](https://play.google.com/store/apps/details?id=hitl.sh.app)) * ✅ Your API key generated and ready * ✅ Basic understanding of REST APIs * ✅ Team members who will act as reviewers (with email addresses) If you haven't created your API key yet, complete this step first. ## Step 1: Create the Loop ### Using the API Create your loop using the HITL.sh API. When you create a loop, you automatically become a member and receive invitation codes for sharing: ```bash cURL theme={null} curl -X POST 'https://api.hitl.sh/v1/api/loops' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check" }' ``` ```python Python theme={null} import requests url = "https://api.hitl.sh/v1/api/loops" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } data = { "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check" } response = requests.post(url, headers=headers, json=data) loop_data = response.json() print(f"✅ Loop created: {loop_data['data']['loop']['id']}") print(f"📱 Invite code: {loop_data['data']['invite_code']}") print(f"🔗 Join URL: {loop_data['data']['join_url']}") ``` ```javascript Node.js theme={null} const axios = require('axios'); const response = await axios.post('https://api.hitl.sh/v1/api/loops', { name: 'Content Moderation Review', description: 'Review user-generated content for community guidelines compliance', icon: 'shield-check' }, { headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' } }); const { loop, invite_code, join_url, qr_code_url } = response.data.data; console.log('✅ Loop created:', loop.id); console.log('📱 Invite code:', invite_code); console.log('🔗 Join URL:', join_url); console.log('📱 QR code URL:', qr_code_url); ``` **Expected Response:** ```json theme={null} { "error": false, "msg": "Loop created successfully with QR code", "data": { "loop": { "id": "65f1234567890abcdef12345", "name": "Content Moderation Review", "description": "Review user-generated content for community guidelines compliance", "icon": "shield-check", "creator_id": "65f1234567890abcdef12346", "member_count": 1, "pending_count": 0, "created_at": "2024-03-15T10:30:00Z" }, "invite_code": "ABC123DEF", "qr_code_base64": "data:image/png;base64,iVBORw0KGg...", "qr_code_url": "https://api.hitl.sh/qr/ABC123DEF.png", "join_url": "https://my.hitl.sh/join/ABC123DEF" } } ``` Save the `invite_code`, `join_url`, and `qr_code_url` from the response - you'll need these to invite reviewers to your loop. ## Step 2: Add Reviewers Your loop needs human reviewers to process requests. Share the invitation information from Step 1 to add team members: ### Invitation Methods Give reviewers the **invite code** (e.g., `ABC123DEF`) to enter in the mobile app. Send the **join URL** via email or chat for one-click joining. Display or share the **QR code image** for quick mobile scanning. Send invitation emails directly through your dashboard. ### Mobile App Setup Reviewers need the HITL.sh mobile app to receive notifications and respond to requests: Reviewers download the HITL.sh mobile app from their app store. Using one of the invitation methods above (code, URL, or QR scan). Ensure push notifications are enabled for instant request alerts. Reviewers should see the loop appear in their app and receive a welcome notification. Step-by-step guide for reviewers to join loops using the mobile app. ## Step 3: Create Your First Request Now that your loop is set up with reviewers, create your first request to test the workflow: ### Request Structure Requests in HITL.sh consist of: * **processing\_type**: `time-sensitive` or `deferred` * **type**: Content type (`markdown` or `image`) * **priority**: `low`, `medium`, `high`, or `critical` * **request\_text**: The main content to review (1-2000 characters) * **response\_type**: Expected response format * **response\_config**: Configuration for the response type * **default\_response**: Fallback response if timeout occurs * **platform**: Source platform creating the request * **image\_url**: Required when type is `image` * **context**: Additional JSON data for context * **timeout\_seconds**: Custom timeout (60-86400 seconds) * **callback\_url**: Webhook URL for response notifications * **platform\_version**: Version of the creating platform ### Response Type Options Configure what decisions reviewers can make: Choose one option (Approve, Reject, Escalate) Select multiple issues or categories Provide detailed written feedback Rate content on a numeric scale Enter specific numeric values ## Step 4: Submit Your First Request Create a test request to verify your loop is working properly. Use the loop ID from Step 1: ```bash cURL theme={null} curl -X POST 'https://api.hitl.sh/v1/api/loops/YOUR_LOOP_ID/requests' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this user comment: \"This post is amazing! Thanks for sharing.\"", "context": { "user_id": "user123", "post_id": "post456", "automated_flags": ["potential_spam"] }, "timeout_seconds": 3600, "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Changes", "Escalate"] }, "default_response": "Approve", "platform": "api", "callback_url": "https://your-app.com/webhook/response" }' ``` ```python Python theme={null} import requests url = f"https://api.hitl.sh/v1/api/loops/{LOOP_ID}/requests" headers = { "Authorization": f"Bearer {YOUR_API_KEY}", "Content-Type": "application/json" } data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this user comment for compliance.", "context": { "user_id": "user123", "post_id": "post456" }, "timeout_seconds": 3600, "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Changes", "Escalate"] }, "default_response": "Approve", "platform": "api" } response = requests.post(url, headers=headers, json=data) request_data = response.json() print(f"✅ Request created: {request_data['data']['request_id']}") print(f"📱 Broadcasted to: {request_data['data']['broadcasted_to']} reviewers") print(f"🔔 Notifications sent: {request_data['data']['notifications_sent']}") ``` ```javascript Node.js theme={null} const axios = require('axios'); const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${loopId}/requests`, { processing_type: 'time-sensitive', type: 'markdown', priority: 'medium', request_text: 'Please review this user comment for compliance.', context: { user_id: 'user123', post_id: 'post456' }, timeout_seconds: 3600, response_type: 'single_select', response_config: { options: ['Approve', 'Reject', 'Needs Changes', 'Escalate'] }, default_response: 'Approve', platform: 'api' }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } } ); console.log('✅ Request created:', response.data.data.request_id); console.log('📱 Broadcasted to:', response.data.data.broadcasted_to, 'reviewers'); ``` **Expected Response:** ```json theme={null} { "error": false, "msg": "Request created and broadcasted successfully", "data": { "request_id": "65f1234567890abcdef12348", "status": "pending", "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "timeout_at": "2024-03-15T11:30:00Z", "broadcasted_to": 4, "notifications_sent": 3, "polling_url": "/v1/api/requests/65f1234567890abcdef12348" } } ``` Your reviewers will immediately receive push notifications on their mobile devices about this new request! ## Step 5: Monitor Request Progress Track your request and wait for reviewer responses: ### Check Request Status Use the polling URL from the response to check status: ```python theme={null} import requests import time def wait_for_response(request_id, api_key, max_wait=300): """Poll for request completion""" url = f"https://api.hitl.sh/v1/api/requests/{request_id}" headers = {"Authorization": f"Bearer {api_key}"} start_time = time.time() while time.time() - start_time < max_wait: response = requests.get(url, headers=headers) data = response.json() status = data['data']['status'] print(f"📊 Request status: {status}") if status == 'completed': response_data = data['data']['response_data'] print(f"✅ Human decision: {response_data}") return response_data elif status == 'cancelled': print("❌ Request was cancelled") return None elif status == 'timed_out': print("⏰ Request timed out, using default response") return data['data']['default_response'] time.sleep(10) # Wait 10 seconds before checking again print("⏰ Polling timeout reached") return None # Check your request response = wait_for_response("YOUR_REQUEST_ID", "YOUR_API_KEY") ``` ### Request Lifecycle Request is created and push notifications sent to all active reviewers. Waiting for a reviewer to respond to the request. Reviewer has submitted their response - human decision is ready! If configured, your callback URL receives the response data. ## Receiving Human Decisions ### Using Webhooks (Recommended) Set up webhooks for real-time notifications when requests complete: ```python theme={null} # Your webhook endpoint @app.route('/webhook/hitl', methods=['POST']) def handle_hitl_webhook(): payload = request.json if payload.get('event') == 'request.completed': request_id = payload['data']['request_id'] response_data = payload['data']['response_data'] print(f"📥 Received response for {request_id}: {response_data}") # Process the human decision if response_data == 'Approve': approve_content(request_id) elif response_data == 'Reject': reject_content(request_id) elif response_data == 'Escalate': escalate_to_manager(request_id) return {'status': 'received'}, 200 ``` ### Using Polling If webhooks aren't available, poll the request status periodically: ```javascript theme={null} async function pollForResponse(requestId) { const maxAttempts = 30; // 5 minutes with 10s intervals for (let i = 0; i < maxAttempts; i++) { try { const response = await axios.get( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': `Bearer ${apiKey}` }} ); const { status, response_data } = response.data.data; if (status === 'completed') { console.log('✅ Human decision received:', response_data); return response_data; } else if (status === 'timed_out') { console.log('⏰ Request timed out'); return null; } // Wait 10 seconds before next poll await new Promise(resolve => setTimeout(resolve, 10000)); } catch (error) { console.error('Error polling request:', error); break; } } console.log('⏰ Polling timeout reached'); return null; } ``` ## Best Practices Provide sufficient context in `request_text` and `context` fields for informed decisions. Set realistic `timeout_seconds` based on request complexity and reviewer availability. Choose safe default responses that represent the best fallback decision. Match response types to your use case - single select for decisions, text for feedback. ### Request Optimization * **Critical**: Security issues, policy violations (\< 15 min response) * **High**: Time-sensitive content decisions (\< 1 hour) * **Medium**: Standard content review (\< 4 hours) * **Low**: Quality improvements, feedback (\< 24 hours) Include relevant context to help reviewers make informed decisions: * User information and history * Automated system flags and confidence scores * Related content or previous decisions * Business context and implications Design response options that are: * **Mutually exclusive** for single select * **Comprehensive** covering all possible decisions * **Clear and actionable** with no ambiguity * **Consistent** with your business logic ## Troubleshooting **Symptoms:** `notifications_sent: 0` in response **Solutions:** * Verify reviewers have joined the loop successfully * Check if reviewers have push notifications enabled * Ensure reviewers are active (not in do-not-disturb mode) * Confirm the mobile app is installed and logged in **Symptoms:** Request created but reviewers don't see it **Solutions:** * Check if `broadcasted_to` count matches expected reviewers * Verify reviewers are members of the correct loop * Ensure loop ID in the request URL is correct * Check if reviewers are filtering requests by priority **Symptoms:** Request status always shows "pending" **Solutions:** * Verify the request ID is correct * Check API key permissions for request access * Ensure you're polling the correct endpoint * Wait for reviewers to actually respond to the request **Symptoms:** No webhook calls when request completes **Solutions:** * Verify `callback_url` is publicly accessible * Check webhook endpoint returns 200 status * Ensure webhook URL uses HTTPS * Test with webhook debugging tools like ngrok ## Next Steps 🎉 **Congratulations!** You've successfully created your first loop and processed a request. Here's what to explore next: Learn about all available response types and their configurations. Help your reviewers master the mobile app interface. Configure webhooks for real-time response notifications. Explore all available API endpoints and advanced features. ### Production Checklist Before deploying to production: * ✅ **Test with multiple reviewers** to ensure proper load distribution * ✅ **Configure webhooks** for automated response processing * ✅ **Set up monitoring** for request volume and response times * ✅ **Train reviewers** on your specific guidelines and criteria * ✅ **Test timeout scenarios** to validate default response handling * ✅ **Implement retry logic** for API calls and webhook handling # Response Types Guide Source: https://docs.hitl.sh/guides/response-types Complete guide to configuring different response types for your HITL requests. Learn how to collect text, selections, ratings, and more from human reviewers. Choose the right response type to collect exactly the information you need from human reviewers. Each response type is optimized for different use cases and provides structured data that's easy to process programmatically. Response types determine how reviewers interact with your requests in the mobile app and what format the response data takes when returned to your application. ## Simple vs Complex Configuration Formats For `single_select` and `multi_select` response types, you can use **two different formats** for the `options` array: **Use string arrays** - Easiest approach for most use cases: ```json theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Review"] } } ``` **What happens:** * API automatically generates clean values: `"approve"`, `"reject"`, `"needs_review"` * These generated values are returned in `response_data` * Labels display to reviewers in the mobile app **Best for:** Quick setup, when you don't need custom value formats **Use objects with value/label** - Full control over returned values: ```json theme={null} { "response_type": "single_select", "response_config": { "options": [ {"value": "approved", "label": "✅ Approve - Safe to publish"}, {"value": "rejected", "label": "❌ Reject - Violates guidelines"}, {"value": "review", "label": "⚠️ Needs Review - Unclear content"} ] } } ``` **What happens:** * You specify exact values to be returned: `"approved"`, `"rejected"`, `"review"` * Labels with emojis/descriptions display to reviewers * Full control over response data format **Best for:** Custom values, database keys, when you want rich labels with emojis **Both formats work identically** - choose based on your preference. Examples below show both formats. ## Response Type Overview **Best for**: Open-ended feedback, explanations, detailed reviews
**Returns**: String value with reviewer's text input
**Best for**: Yes/No decisions, choosing one option from a list
**Returns**: String value of the selected option
**Best for**: Selecting multiple items, feature identification, tagging
**Returns**: Array of selected option strings
**Best for**: Quality assessment, scoring content, performance evaluation
**Returns**: Number value within configured range
**Best for**: Quantities, measurements, counting tasks
**Returns**: Number value with optional validation
## Text Response Perfect for collecting detailed feedback, explanations, and open-ended responses from reviewers. ### Configuration ```json Basic Text theme={null} { "response_type": "text", "response_config": { "placeholder": "Enter your feedback here...", "max_length": 500, "required": true } } ``` ```json Text with Guidelines theme={null} { "response_type": "text", "response_config": { "placeholder": "Provide detailed feedback on the content quality...", "max_length": 1000, "min_length": 50, "required": true, "guidelines": "Please explain your reasoning and provide specific examples." } } ``` ### Configuration Options Hint text shown in the input field Maximum character limit (default: 1000, max: 5000) Minimum character requirement (default: 0) Whether response is required (default: true) Additional instructions displayed to reviewers ### Use Cases & Examples ```python theme={null} # AI-generated content review text_config = { "response_type": "text", "response_config": { "placeholder": "Explain what makes this content high or low quality...", "max_length": 800, "min_length": 100, "guidelines": "Consider accuracy, clarity, usefulness, and engagement factors." } } # Example response: "The content is well-structured and informative, but contains several factual errors about renewable energy statistics that need correction." ``` ```python theme={null} # Support ticket resolution support_config = { "response_type": "text", "response_config": { "placeholder": "Describe how you would resolve this customer issue...", "max_length": 600, "guidelines": "Include specific steps and any additional resources needed." } } # Example response: "1. Issue refund within 24 hours 2. Send apology email with discount code 3. Follow up in 1 week to ensure satisfaction" ``` ```python theme={null} # Software code review code_review_config = { "response_type": "text", "response_config": { "placeholder": "Provide code review feedback...", "max_length": 1200, "min_length": 50, "guidelines": "Focus on correctness, performance, security, and maintainability." } } # Example response: "Good use of error handling. Consider extracting the validation logic into a separate function for reusability. Line 45 has a potential memory leak." ``` ## Single Select Ideal for binary decisions or choosing one option from multiple choices. ### Configuration ```json Simple Format (String Array) theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Review"], "required": true }, "default_response": "reject" } // Response data will be: "approve", "reject", or "needs_review" // (auto-generated: lowercase, spaces -> underscores) ``` ```json Complex Format (Value/Label Objects) theme={null} { "response_type": "single_select", "response_config": { "options": [ {"value": "high_priority", "label": "🔴 High Priority - Immediate attention"}, {"value": "medium_priority", "label": "🟡 Medium - Address within 24 hours"}, {"value": "low_priority", "label": "🟢 Low - Can wait for next batch"} ], "required": true }, "default_response": "low_priority" } // Response data will be: "high_priority", "medium_priority", or "low_priority" // (exact values you specified) ``` ### Configuration Options Array of option strings to choose from (2-10 options recommended) Whether a selection is required (default: true) Allow reviewers to enter custom text option (default: false) Randomize option display order to reduce bias (default: false) ### Use Cases & Examples ```python theme={null} # User-generated content approval moderation_config = { "response_type": "single_select", "response_config": { "options": [ "✅ Approve - Follows community guidelines", "⚠️ Approve with Warning - Minor guideline issues", "❌ Reject - Violates guidelines", "🚨 Reject and Flag - Serious violation" ], "randomize_order": false # Keep logical order } } # Example response: "❌ Reject - Violates guidelines" ``` ```python theme={null} # Legal document categorization classification_config = { "response_type": "single_select", "response_config": { "options": [ "Contract", "Invoice", "Legal Notice", "Insurance Claim", "Other Business Document" ], "allow_other": true, # Allow custom categories "randomize_order": true # Reduce bias } } # Example response: "Contract" ``` ```python theme={null} # Translation quality review translation_config = { "response_type": "single_select", "response_config": { "options": [ "Excellent - Perfect translation", "Good - Minor improvements needed", "Fair - Several issues to fix", "Poor - Needs complete rework" ] } } # Example response: "Good - Minor improvements needed" ``` ## Multi Select Perfect when reviewers need to select multiple items or identify several features. ### Configuration ```json Simple Format (String Array) theme={null} { "response_type": "multi_select", "response_config": { "options": ["Grammar Issues", "Factual Errors", "Tone Problems", "Formatting Issues"], "min_selections": 0, "max_selections": 4, "required": false }, "default_response": [] } // Response data will be array like: ["grammar_issues", "tone_problems"] // (auto-generated: lowercase, spaces -> underscores) ``` ```json Complex Format (Value/Label Objects) theme={null} { "response_type": "multi_select", "response_config": { "options": [ {"value": "pii", "label": "🔒 Contains Personal Information"}, {"value": "external_links", "label": "🔗 Includes External Links"}, {"value": "promo", "label": "📢 Has Promotional Content"}, {"value": "sensitive", "label": "⚠️ Contains Sensitive Topics"}, {"value": "code", "label": "💻 Includes Code/Technical Content"} ], "min_selections": 1, "max_selections": 3 }, "default_response": [] } // Response data will be array like: ["pii", "external_links", "sensitive"] // (exact values you specified) ``` ### Configuration Options Array of selectable options (3-15 options recommended) Minimum number of selections required (default: 0) Maximum selections allowed (default: unlimited) Whether at least one selection is required (default: false) Allow custom text entries (default: false) ### Use Cases & Examples ```python theme={null} # Identify multiple issues in content issue_detection = { "response_type": "multi_select", "response_config": { "options": [ "Spelling/Grammar Errors", "Factual Inaccuracies", "Inappropriate Tone", "Missing Information", "Poor Structure", "Copyright Issues" ], "min_selections": 0, # Issues are optional "max_selections": 6, "allow_other": true } } # Example response: ["Spelling/Grammar Errors", "Missing Information"] ``` ```python theme={null} # Verify product features mentioned feature_check = { "response_type": "multi_select", "response_config": { "options": [ "Free Shipping", "24/7 Support", "Money-back Guarantee", "Mobile App Available", "International Shipping", "Bulk Discounts" ], "min_selections": 1, "max_selections": 6 } } # Example response: ["Free Shipping", "Money-back Guarantee", "Mobile App Available"] ``` ```python theme={null} # Identify elements in an image image_analysis = { "response_type": "multi_select", "response_config": { "options": [ "People", "Text/Writing", "Logos/Branding", "Products", "Buildings/Architecture", "Nature/Landscape", "Vehicles" ], "min_selections": 1, "max_selections": 4 } } # Example response: ["People", "Products", "Logos/Branding"] ``` ## Rating Response Collect numerical ratings and scores from reviewers for quantitative assessment. ### Configuration ```json 5-Star Rating theme={null} { "response_type": "rating", "response_config": { "scale_max": 5 } } ``` **Note:** Only `scale_max` is required. Defaults: `scale_min: 1`, `scale_step: 1`, `required: false` ```json 10-Point Scale theme={null} { "response_type": "rating", "response_config": { "scale_min": 0, "scale_max": 10, "scale_step": 1 } } ``` ### Configuration Options Maximum rating value (typically 5 or 10) Minimum rating value (typically 0 or 1) Rating increment (default: 1, can use 0.5 for half-stars) Whether a rating is required ### Use Cases & Examples ```python theme={null} # Rate AI-generated article quality quality_rating = { "response_type": "rating", "response_config": { "scale_max": 5 # scale_min defaults to 1 # scale_step defaults to 1 } } # Example response: 4 ``` ```python theme={null} # Rate customer interaction quality service_rating = { "response_type": "rating", "response_config": { "scale_max": 10, "scale_step": 1 # scale_min defaults to 1 } } # Example response: 8 ``` ```python theme={null} # Rate translation accuracy with half-points translation_rating = { "response_type": "rating", "response_config": { "scale_max": 5, "scale_step": 0.5 # Allow half-star ratings # scale_min defaults to 1 } } # Example response: 3.5 ``` ## Number Input Collect specific numerical values like counts, measurements, or quantities. ### Configuration ```json Basic Number Input theme={null} { "response_type": "number", "response_config": { "max_value": 1000 } } ``` **Note:** Only `max_value` is required. Defaults: `min_value: 1`, `decimal_places: 2`, `allow_negative: false`, `required: false` ```json Decimal Numbers theme={null} { "response_type": "number", "response_config": { "min_value": 0, "max_value": 100, "decimal_places": 1, "allow_negative": false } } ``` ### Configuration Options Maximum allowed value Minimum allowed value Number of decimal places allowed (0-10) Whether negative numbers are allowed Whether input is required ### Use Cases & Examples ```python theme={null} # Count specific elements in content counting_config = { "response_type": "number", "response_config": { "min_value": 0, "max_value": 50, "decimal_places": 0 # Whole numbers only } } # Example response: 7 ``` ```python theme={null} # Measure response time or performance performance_config = { "response_type": "number", "response_config": { "min_value": 0, "max_value": 60, "decimal_places": 1 } } # Example response: 3.2 ``` ```python theme={null} # Verify pricing information price_config = { "response_type": "number", "response_config": { "min_value": 0, "max_value": 10000 # decimal_places defaults to 2 (perfect for currency) } } # Example response: 49.99 ``` ## Advanced Response Configurations ### Combining Multiple Response Types For complex reviews, create multiple requests with different response types: ```python theme={null} # Multi-stage content review def comprehensive_content_review(content, loop_id): # Stage 1: Overall quality rating quality_request = create_request({ "loop_id": loop_id, "request_text": f"Rate the overall quality of this content:\n\n{content}", "response_type": "rating", "response_config": { "scale_max": 5 # scale_min defaults to 1 }, "metadata": {"stage": "quality_rating"} }) # Stage 2: Issue identification issues_request = create_request({ "loop_id": loop_id, "request_text": f"Identify any issues in this content:\n\n{content}", "response_type": "multi_select", "response_config": { "options": ["Grammar", "Factual Errors", "Tone", "Structure"], "min_selections": 0 }, "metadata": {"stage": "issue_detection"} }) # Stage 3: Detailed feedback feedback_request = create_request({ "loop_id": loop_id, "request_text": f"Provide detailed improvement suggestions:\n\n{content}", "response_type": "text", "response_config": { "min_length": 50, "max_length": 500 }, "metadata": {"stage": "detailed_feedback"} }) return [quality_request, issues_request, feedback_request] ``` ### Dynamic Response Configuration Adjust response types based on content characteristics: ```python theme={null} def get_dynamic_response_config(content_type, content_length): """Return appropriate response config based on content""" if content_type == "image": return { "response_type": "multi_select", "response_config": { "options": ["People", "Text", "Logos", "Products", "Inappropriate Content"], "min_selections": 1 } } elif content_type == "short_text" and content_length < 100: return { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs More Info"] } } else: # Long form content return { "response_type": "text", "response_config": { "min_length": 100, "max_length": 1000, "placeholder": "Provide detailed feedback..." } } ``` ### Response Validation Add custom validation for response data: ```python theme={null} def validate_response_data(response_type, response_data, config): """Validate response data meets requirements""" if response_type == "rating": min_val = config.get("scale_min", 1) max_val = config.get("scale_max", 5) if not (min_val <= response_data <= max_val): raise ValueError(f"Rating must be between {min_val} and {max_val}") elif response_type == "text": min_len = config.get("min_length", 0) max_len = config.get("max_length", 1000) if not (min_len <= len(response_data) <= max_len): raise ValueError(f"Text length must be {min_len}-{max_len} characters") elif response_type == "multi_select": min_sel = config.get("min_selections", 0) max_sel = config.get("max_selections", len(config["options"])) if not (min_sel <= len(response_data) <= max_sel): raise ValueError(f"Must select {min_sel}-{max_sel} options") return True ``` ## Best Practices ### Response Type Selection **Text**: Use for subjective feedback, explanations, or when you need qualitative insights **Single Select**: Perfect for binary decisions or when one clear choice is needed **Multi Select**: When multiple aspects need to be identified or tagged **Rating**: For quantitative assessment or when you need to compare/rank items **Number**: When you need specific measurements, counts, or calculations * Keep option lists concise (max 8-10 options for single/multi select) * Use clear, descriptive labels that work on small screens * Provide appropriate placeholder text and guidelines * Test response times on mobile devices * Use consistent response types within similar request categories * Provide clear instructions and examples * Use logical ordering for options (e.g., severity levels) * Consider randomizing options to reduce position bias * Set appropriate validation rules (min/max lengths, value ranges) * Use required fields judiciously - only for truly essential data * Provide "Other" or "Not Applicable" options when appropriate * Include quality checks in your webhook processing ### Response Processing ```python theme={null} class ResponseProcessor: def process_response(self, request_data, response_data): """Process different response types appropriately""" response_type = request_data['response_type'] if response_type == 'rating': return self.process_rating(response_data, request_data['response_config']) elif response_type == 'text': return self.process_text(response_data, request_data['response_config']) elif response_type == 'multi_select': return self.process_multi_select(response_data, request_data['response_config']) # ... handle other types def process_rating(self, rating, config): """Convert rating to actionable insights""" min_val = config.get('scale_min', 1) max_val = config.get('scale_max', 5) # Normalize to 0-1 scale normalized = (rating - min_val) / (max_val - min_val) # Categorize rating if normalized >= 0.8: category = "excellent" elif normalized >= 0.6: category = "good" elif normalized >= 0.4: category = "average" elif normalized >= 0.2: category = "poor" else: category = "unacceptable" return { "raw_rating": rating, "normalized_score": normalized, "category": category, "actionable": category in ["poor", "unacceptable"] } def process_text(self, text, config): """Extract insights from text responses""" import re # Basic sentiment analysis (you'd use a proper library) positive_words = ["good", "excellent", "great", "perfect", "approve"] negative_words = ["poor", "bad", "terrible", "reject", "inappropriate"] positive_count = sum(1 for word in positive_words if word in text.lower()) negative_count = sum(1 for word in negative_words if word in text.lower()) # Extract action items (sentences with "should", "need", "must") action_pattern = r'[^.!?]*(?:should|need|must)[^.!?]*[.!?]' action_items = re.findall(action_pattern, text, re.IGNORECASE) return { "text": text, "word_count": len(text.split()), "sentiment_score": positive_count - negative_count, "action_items": action_items, "has_specific_feedback": len(action_items) > 0 } ``` ## Next Steps Try different response types in our step-by-step tutorial. See all available options for creating requests with different response types. Learn how to integrate HITL.sh into your application with practical examples. Step-by-step guide to creating your first loop and sending requests. # Simple Integration Guide Source: https://docs.hitl.sh/guides/simple-integration Learn how to easily integrate HITL.sh into your application with practical examples for common scenarios # Simple Integration Guide This guide shows you how to integrate HITL.sh into your applications to get human input when your automated systems need help making decisions. Perfect for developers who want to add human oversight to AI systems, content moderation, quality checks, and approval workflows. ## What You'll Learn Get your API key and understand the core concepts Learn the most useful integration patterns with copy-paste examples Process human responses in your application code Tips for reliable production integration ## Prerequisites 1. Sign up at [app.hitl.sh](https://app.hitl.sh) 2. Create a new API key in your dashboard 3. Set up a loop with team members who will review requests ```bash theme={null} export HITL_API_KEY="your_api_key_here" export HITL_LOOP_ID="your_loop_id_here" ``` ```bash Python theme={null} pip install requests ``` ```bash Node.js theme={null} npm install axios ``` ```bash cURL theme={null} # No installation needed - cURL comes with most systems ``` ## Integration Pattern #1: Content Moderation Perfect for reviewing user-generated content, AI outputs, or flagged posts. ```python Python theme={null} import requests import time def review_content(content, priority="medium"): """Send content for human review and return the decision""" # Create the review request response = requests.post( f"https://api.hitl.sh/v1/api/loops/{HITL_LOOP_ID}/requests", headers={ "Authorization": f"Bearer {HITL_API_KEY}", "Content-Type": "application/json" }, json={ "processing_type": "time-sensitive", "type": "markdown", "priority": priority, "request_text": f"Please review this content for community guidelines compliance:\n\n{content}", "timeout_seconds": 1800, # 30 minutes "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "Approve - Safe to publish"}, {"value": "reject", "label": "Reject - Violates guidelines"}, {"value": "needs_review", "label": "Needs Review - Unclear content"} ], "required": true }, "default_response": "reject", # Safe default "platform": "api" } ) if response.status_code != 201: return {"error": "Failed to create request"} request_data = response.json() request_id = request_data["data"]["request_id"] # Poll for response (in production, use webhooks instead) return wait_for_response(request_id) def wait_for_response(request_id, max_wait_minutes=30): """Wait for human response (use webhooks in production)""" for _ in range(max_wait_minutes * 2): # Check every 30 seconds response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": f"Bearer {HITL_API_KEY}"} ) if response.status_code == 200: data = response.json()["data"]["request"] if data["status"] == "completed": return { "decision": data["response_data"], "reviewed_by": data.get("response_by_user", {}).get("name", "Unknown"), "response_time": data.get("response_time_seconds", 0) } elif data["status"] in ["timeout", "cancelled"]: return {"decision": data["default_response"], "timeout": True} time.sleep(30) # Wait 30 seconds before next check return {"error": "Timeout waiting for response"} # Usage example if __name__ == "__main__": user_post = "Check out this amazing deal on cryptocurrency!" result = review_content(user_post, priority="high") if result.get("decision") == "approve": print("✅ Content approved - publish it!") elif result.get("decision") == "reject": print("❌ Content rejected - don't publish") else: print(f"⚠️ Content needs review: {result}") ``` ```javascript Node.js theme={null} const axios = require('axios'); async function reviewContent(content, priority = 'medium') { try { // Create the review request const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${process.env.HITL_LOOP_ID}/requests`, { processing_type: 'time-sensitive', type: 'markdown', priority: priority, request_text: `Please review this content:\n\n${content}`, timeout_seconds: 1800, // 30 minutes response_type: 'single_select', response_config: { options: [ {value: 'approve', label: 'Approve - Safe to publish'}, {value: 'reject', label: 'Reject - Violates guidelines'}, {value: 'needs_review', label: 'Needs Review - Unclear content'} ], required: true }, default_response: 'reject', // Safe default platform: 'api' }, { headers: { 'Authorization': `Bearer ${process.env.HITL_API_KEY}`, 'Content-Type': 'application/json' } } ); const requestId = response.data.data.request_id; // Poll for response (in production, use webhooks instead) return await waitForResponse(requestId); } catch (error) { return { error: 'Failed to create request', details: error.message }; } } async function waitForResponse(requestId, maxWaitMinutes = 30) { for (let i = 0; i < maxWaitMinutes * 2; i++) { try { const response = await axios.get( `https://api.hitl.sh/v1/api/requests/${requestId}`, { headers: { 'Authorization': `Bearer ${process.env.HITL_API_KEY}` } } ); const request = response.data.data.request; if (request.status === 'completed') { return { decision: request.response_data, reviewed_by: request.response_by_user?.name || 'Unknown', response_time: request.response_time_seconds || 0 }; } else if (['timeout', 'cancelled'].includes(request.status)) { return { decision: request.default_response, timeout: true }; } } catch (error) { console.error('Error checking request status:', error.message); } // Wait 30 seconds before next check await new Promise(resolve => setTimeout(resolve, 30000)); } return { error: 'Timeout waiting for response' }; } // Usage example async function main() { const userPost = "Check out this amazing deal on cryptocurrency!"; const result = await reviewContent(userPost, 'high'); if (result.decision === 'approve') { console.log('✅ Content approved - publish it!'); } else if (result.decision === 'reject') { console.log('❌ Content rejected - don\'t publish'); } else { console.log(`⚠️ Content needs review: ${JSON.stringify(result)}`); } } // Uncomment to run // main(); ``` ```bash cURL theme={null} #!/bin/bash # Set your credentials HITL_API_KEY="your_api_key_here" HITL_LOOP_ID="your_loop_id_here" # Content to review CONTENT="Check out this amazing deal on cryptocurrency!" echo "🔄 Sending content for human review..." # Create review request RESPONSE=$(curl -s -X POST "https://api.hitl.sh/v1/api/loops/$HITL_LOOP_ID/requests" \ -H "Authorization: Bearer $HITL_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"processing_type\": \"time-sensitive\", \"type\": \"markdown\", \"priority\": \"high\", \"request_text\": \"Please review this content for community guidelines compliance:\\n\\n$CONTENT\", \"timeout_seconds\": 1800, \"response_type\": \"single_select\", \"response_config\": { \"options\": [ {\"value\": \"approve\", \"label\": \"Approve - Safe to publish\"}, {\"value\": \"reject\", \"label\": \"Reject - Violates guidelines\"}, {\"value\": \"needs_review\", \"label\": \"Needs Review - Unclear content\"} ], \"required\": true }, \"default_response\": \"reject\", \"platform\": \"api\" }") # Extract request ID REQUEST_ID=$(echo $RESPONSE | jq -r '.data.request_id') if [ "$REQUEST_ID" == "null" ]; then echo "❌ Failed to create request" echo $RESPONSE exit 1 fi echo "✅ Request created with ID: $REQUEST_ID" echo "⏳ Waiting for human reviewer response..." # Poll for response (check every 30 seconds for up to 30 minutes) for i in {1..60}; do STATUS_RESPONSE=$(curl -s -X GET "https://api.hitl.sh/v1/api/requests/$REQUEST_ID" \ -H "Authorization: Bearer $HITL_API_KEY") STATUS=$(echo $STATUS_RESPONSE | jq -r '.data.request.status') if [ "$STATUS" == "completed" ]; then DECISION=$(echo $STATUS_RESPONSE | jq -r '.data.request.response_data') REVIEWER=$(echo $STATUS_RESPONSE | jq -r '.data.request.response_by_user.name // "Unknown"') echo "🎉 Review completed by $REVIEWER" echo "📋 Decision: $DECISION" if [ "$DECISION" == "approve" ]; then echo "✅ Content approved - safe to publish!" elif [ "$DECISION" == "reject" ]; then echo "❌ Content rejected - do not publish" else echo "⚠️ Content needs review before publishing" fi break elif [ "$STATUS" == "timeout" ] || [ "$STATUS" == "cancelled" ]; then echo "⏰ Request timed out or was cancelled" echo "🛡️ Using safe default: Reject" break fi echo "⏳ Still waiting... (attempt $i/60)" sleep 30 done ``` ## Integration Pattern #2: AI Output Quality Check Perfect for reviewing AI-generated content, translations, or automated responses before sending them to users. ```python Python theme={null} def review_ai_output(ai_response, context="", min_quality=3): """Review AI output quality and get human feedback""" response = requests.post( f"https://api.hitl.sh/v1/api/loops/{HITL_LOOP_ID}/requests", headers={ "Authorization": f"Bearer {HITL_API_KEY}", "Content-Type": "application/json" }, json={ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": f"Rate this AI response quality (1-5 scale):\n\nContext: {context}\n\nAI Response: {ai_response}", "timeout_seconds": 2400, # 40 minutes "response_type": "rating", "response_config": { "min": 1, "max": 5 }, "default_response": 2, # Conservative default "platform": "api" } ) if response.status_code != 201: return {"error": "Failed to create request"} request_data = response.json() request_id = request_data["data"]["request_id"] result = wait_for_response(request_id) if isinstance(result.get("decision"), (int, float)): return { "quality_score": result["decision"], "approved": result["decision"] >= min_quality, "reviewer": result.get("reviewed_by"), "recommendation": "approve" if result["decision"] >= min_quality else "revise" } return result # Usage ai_content = "The weather today is quite pleasant with sunny skies." context = "Customer asked about today's weather" quality_check = review_ai_output(ai_content, context, min_quality=3) if quality_check.get("approved"): print(f"✅ AI response approved (score: {quality_check['quality_score']}/5)") # Send AI response to customer else: print(f"❌ AI response needs improvement (score: {quality_check['quality_score']}/5)") # Generate new AI response or escalate to human agent ``` ```javascript Node.js theme={null} async function reviewAiOutput(aiResponse, context = '', minQuality = 3) { try { const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${process.env.HITL_LOOP_ID}/requests`, { processing_type: 'time-sensitive', type: 'markdown', priority: 'medium', request_text: `Rate this AI response quality (1-5 scale):\n\nContext: ${context}\n\nAI Response: ${aiResponse}`, timeout_seconds: 2400, // 40 minutes response_type: 'rating', response_config: { min: 1, max: 5 }, default_response: 2, // Conservative default platform: 'api' }, { headers: { 'Authorization': `Bearer ${process.env.HITL_API_KEY}`, 'Content-Type': 'application/json' } } ); const requestId = response.data.data.request_id; const result = await waitForResponse(requestId); if (typeof result.decision === 'number') { return { quality_score: result.decision, approved: result.decision >= minQuality, reviewer: result.reviewed_by, recommendation: result.decision >= minQuality ? 'approve' : 'revise' }; } return result; } catch (error) { return { error: 'Failed to create request', details: error.message }; } } // Usage async function checkAiQuality() { const aiContent = "The weather today is quite pleasant with sunny skies."; const context = "Customer asked about today's weather"; const qualityCheck = await reviewAiOutput(aiContent, context, 3); if (qualityCheck.approved) { console.log(`✅ AI response approved (score: ${qualityCheck.quality_score}/5)`); // Send AI response to customer } else { console.log(`❌ AI response needs improvement (score: ${qualityCheck.quality_score}/5)`); // Generate new AI response or escalate to human agent } } ``` ## Integration Pattern #3: Document Approval Workflow Great for reviewing contracts, proposals, marketing materials, or any documents that need human approval. ```python Python theme={null} def approve_document(document_title, document_content, urgency="medium"): """Send document for approval with detailed feedback""" response = requests.post( f"https://api.hitl.sh/v1/api/loops/{HITL_LOOP_ID}/requests", headers={ "Authorization": f"Bearer {HITL_API_KEY}", "Content-Type": "application/json" }, json={ "processing_type": "time-sensitive" if urgency == "high" else "deferred", "type": "markdown", "priority": urgency, "request_text": f"Please review this document for approval:\n\n**Title:** {document_title}\n\n**Content:**\n{document_content}", "timeout_seconds": 3600 if urgency == "high" else 86400, # 1 hour vs 24 hours "response_type": "multi_select", "response_config": { "options": [ {"value": "approve_as_is", "label": "Approve as-is"}, {"value": "approve_minor_changes", "label": "Approve with minor changes"}, {"value": "needs_major_revisions", "label": "Needs major revisions"}, {"value": "legal_review_required", "label": "Legal review required"}, {"value": "reject_start_over", "label": "Reject - start over"} ], "min_selections": 1, "max_selections": 5, "required": true }, "default_response": ["needs_major_revisions"], # Conservative default "platform": "api" } ) if response.status_code != 201: return {"error": "Failed to create request"} request_data = response.json() request_id = request_data["data"]["request_id"] return wait_for_response(request_id) # Usage doc_title = "Q4 Marketing Proposal" doc_content = """ ## Objective Increase brand awareness by 25% through targeted social media campaigns. ## Budget $50,000 for Q4 campaigns ## Timeline October 1 - December 31, 2024 """ approval = approve_document(doc_title, doc_content, urgency="high") if isinstance(approval.get("decision"), list): decisions = approval["decision"] if "approve_as_is" in decisions: print("✅ Document fully approved - proceed with implementation") elif "approve_minor_changes" in decisions: print("⚠️ Document approved with minor changes needed") elif "legal_review_required" in decisions: print("⚖️ Document needs legal review before approval") else: print(f"❌ Document needs work: {', '.join(decisions)}") ``` ```javascript Node.js theme={null} async function approveDocument(documentTitle, documentContent, urgency = 'medium') { try { const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${process.env.HITL_LOOP_ID}/requests`, { processing_type: urgency === 'high' ? 'time-sensitive' : 'deferred', type: 'markdown', priority: urgency, request_text: `Please review this document for approval:\n\n**Title:** ${documentTitle}\n\n**Content:**\n${documentContent}`, timeout_seconds: urgency === 'high' ? 3600 : 86400, // 1 hour vs 24 hours response_type: 'multi_select', response_config: { options: [ {value: 'approve_as_is', label: 'Approve as-is'}, {value: 'approve_minor_changes', label: 'Approve with minor changes'}, {value: 'needs_major_revisions', label: 'Needs major revisions'}, {value: 'legal_review_required', label: 'Legal review required'}, {value: 'reject_start_over', label: 'Reject - start over'} ], min_selections: 1, max_selections: 5, required: true }, default_response: ['needs_major_revisions'], // Conservative default platform: 'api' }, { headers: { 'Authorization': `Bearer ${process.env.HITL_API_KEY}`, 'Content-Type': 'application/json' } } ); const requestId = response.data.data.request_id; return await waitForResponse(requestId); } catch (error) { return { error: 'Failed to create request', details: error.message }; } } // Usage async function reviewDocument() { const docTitle = "Q4 Marketing Proposal"; const docContent = ` ## Objective Increase brand awareness by 25% through targeted social media campaigns. ## Budget $50,000 for Q4 campaigns ## Timeline October 1 - December 31, 2024 `; const approval = await approveDocument(docTitle, docContent, 'high'); if (Array.isArray(approval.decision)) { const decisions = approval.decision; if (decisions.includes('approve_as_is')) { console.log('✅ Document fully approved - proceed with implementation'); } else if (decisions.includes('approve_minor_changes')) { console.log('⚠️ Document approved with minor changes needed'); } else if (decisions.includes('legal_review_required')) { console.log('⚖️ Document needs legal review before approval'); } else { console.log(`❌ Document needs work: ${decisions.join(', ')}`); } } } ``` ## Using Webhooks (Recommended for Production) Instead of polling for responses, set up webhooks to get notified instantly when reviews complete. Add a `callback_url` field when creating your request: ```python theme={null} # Create request with webhook callback response = requests.post( f"https://api.hitl.sh/v1/api/loops/{HITL_LOOP_ID}/requests", headers={"Authorization": f"Bearer {HITL_API_KEY}"}, json={ "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Review this content...", "timeout_seconds": 1800, "response_type": "single_select", "response_config": {"options": [...]}, "default_response": "reject", "platform": "api", "callback_url": "https://your-domain.com/hitl-webhook" # Your webhook endpoint } ) ``` Then set up an endpoint to receive the webhook notifications: ```python Flask theme={null} from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/hitl-webhook', methods=['POST']) def handle_hitl_response(): """Handle webhook notifications from HITL.sh""" webhook_data = request.json if webhook_data.get('event') == 'request.completed': request_id = webhook_data['data']['request_id'] decision = webhook_data['data']['response_data'] reviewer = webhook_data['data']['response_by_user']['name'] # Process the decision in your application process_human_decision(request_id, decision, reviewer) return jsonify({"status": "success"}) return jsonify({"status": "ignored"}) def process_human_decision(request_id, decision, reviewer): """Process the human decision in your application""" print(f"Request {request_id} completed by {reviewer}: {decision}") # Add your business logic here: # - Update database # - Send notifications # - Trigger next steps in workflow # - Log the decision if __name__ == '__main__': app.run(host='0.0.0.0', port=3000) ``` ```javascript Express theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/hitl-webhook', (req, res) => { const webhookData = req.body; if (webhookData.event === 'request.completed') { const requestId = webhookData.data.request_id; const decision = webhookData.data.response_data; const reviewer = webhookData.data.response_by_user.name; // Process the decision in your application processHumanDecision(requestId, decision, reviewer); res.json({ status: 'success' }); } else { res.json({ status: 'ignored' }); } }); function processHumanDecision(requestId, decision, reviewer) { console.log(`Request ${requestId} completed by ${reviewer}:`, decision); // Add your business logic here: // - Update database // - Send notifications // - Trigger next steps in workflow // - Log the decision } app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` ## Production Best Practices * Store your API key securely (environment variables, secret managers) * Use HTTPS for all webhook endpoints * Validate webhook signatures if available * Implement rate limiting on your webhook endpoints ```python theme={null} import os from cryptography.fernet import Fernet # Secure API key storage HITL_API_KEY = os.environ.get('HITL_API_KEY') if not HITL_API_KEY: raise ValueError("HITL_API_KEY environment variable is required") ``` * Use webhooks instead of polling in production * Implement retry logic with exponential backoff * Set appropriate timeouts for different request types * Cache frequently used loop IDs and configurations ```python theme={null} import time import random def create_request_with_retry(request_data, max_retries=3): """Create request with retry logic""" for attempt in range(max_retries): try: response = requests.post( f"https://api.hitl.sh/v1/api/loops/{HITL_LOOP_ID}/requests", headers={"Authorization": f"Bearer {HITL_API_KEY}"}, json=request_data, timeout=30 ) if response.status_code == 201: return response.json() except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise e # Exponential backoff with jitter wait_time = (2 ** attempt) + random.uniform(0, 1) time.sleep(wait_time) raise Exception("Max retries exceeded") ``` * Log all HITL requests with unique identifiers * Monitor response times and success rates * Set up alerts for timeout rates above acceptable thresholds * Track reviewer performance and availability ```python theme={null} import logging from datetime import datetime # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def log_hitl_request(request_type, content_summary, request_id=None): """Log HITL request for monitoring""" log_data = { "timestamp": datetime.utcnow().isoformat(), "request_type": request_type, "content_summary": content_summary[:100] + "..." if len(content_summary) > 100 else content_summary, "request_id": request_id, "status": "created" if request_id else "failed" } logger.info(f"HITL Request: {log_data}") ``` * Provide clear, specific instructions to reviewers * Use appropriate default responses for timeout scenarios * Test different response types to find what works best * Regularly review and update your request templates ```python theme={null} def create_clear_request(content, request_type="approval"): """Create clear, actionable requests for better response quality""" templates = { "approval": { "instructions": "Please review this content and decide if it's safe to publish:", "options": ["✅ Approve - Safe to publish", "❌ Reject - Do not publish", "⚠️ Needs changes - Specify in comments"] }, "quality": { "instructions": "Rate the quality of this content (1=Poor, 5=Excellent):", "context": "Consider accuracy, clarity, usefulness, and engagement" } } template = templates.get(request_type, templates["approval"]) return { "request_text": f"{template['instructions']}\n\n{content}", "response_config": template.get("options", {}), "context": {"template_used": request_type} } ``` ## Next Steps Configure real-time notifications for production use Explore all available options and configurations Learn about advanced response configurations ## Common Issues & Solutions **Common causes:** * Invalid API key or loop ID * Missing required fields * Invalid response configuration **Solution:** ```python theme={null} # Validate configuration before sending def validate_request_config(config): required_fields = ["processing_type", "type", "priority", "request_text", "response_type", "platform"] for field in required_fields: if field not in config: raise ValueError(f"Missing required field: {field}") if config["response_type"] == "single_select" and not config.get("response_config", {}).get("options"): raise ValueError("Single select requires options array") return True ``` **Common causes:** * Timeout too short for request complexity * Reviewers not available or not notified * Unclear instructions leading to hesitation **Solution:** ```python theme={null} # Adjust timeouts based on request complexity def calculate_timeout(request_type, content_length): base_timeout = { "simple_approval": 1800, # 30 minutes "detailed_review": 3600, # 1 hour "complex_analysis": 7200 # 2 hours } timeout = base_timeout.get(request_type, 1800) # Add extra time for longer content if content_length > 1000: timeout += 1800 # Add 30 minutes return min(timeout, 86400) # Max 24 hours ``` **Common causes:** * Loop members haven't installed the mobile app * Notification permissions disabled * Device tokens expired **Solution:** * Ensure all reviewers have installed the HITL mobile app * Test notifications in your loop settings * Verify reviewers have enabled push notifications * Consider SMS or email fallback notifications # Welcome Source: https://docs.hitl.sh/index Add human oversight to your AI workflows with our mobile-first platform. Create loops, send requests for human review, and receive structured responses via API.
Decorative background image. Decorative background image.

HITL.sh

Add human oversight to your AI workflows with our mobile-first platform. Create loops, send requests for human review, and receive structured responses via API.
# Coming Soon Source: https://docs.hitl.sh/integrations/coming-soon Upcoming integrations and features for HITL.sh - MCP Server, LangChain, and more # Coming Soon We're constantly expanding HITL.sh's integration ecosystem. Here's what's currently in development and coming soon. Before configuring any integration, install the HITL.sh mobile app ([App Store](https://apps.apple.com/us/app/hitl-human-in-the-loop/id6752878072) | [Google Play](https://play.google.com/store/apps/details?id=hitl.sh.app)) since it's an integral part of the setup. ## In Development ### MCP Server Integration Connect HITL.sh with AI assistants like Claude, GPT, and other LLMs using the Model Context Protocol for seamless human-in-the-loop AI workflows. **Status:** In Active Development **Features:** * Direct integration with Claude Desktop and other MCP-compatible AI assistants * Add human oversight to AI agent workflows * Context sharing between AI and human reviewers * Seamless handoff from AI to human decision-making **Use Cases:** * AI agents requesting human approval for critical decisions * Quality control for AI-generated content * Human verification of automated actions * Escalation from AI to human experts when confidence is low **Installation (Coming Soon):** ```bash theme={null} npm install @hitl/mcp-server ``` **Configuration (Preview):** ```json theme={null} { "mcpServers": { "hitl": { "command": "npx", "args": ["-y", "@hitl/mcp-server"], "env": { "HITL_API_KEY": "your_api_key_here" } } } } ``` ## Planned Integrations Python and JavaScript integration for LangChain-based AI applications Workflow orchestration with human decision points Low-code internal tools with human approval workflows Serverless integration platform support ## Request an Integration Have a specific integration you'd like to see? Let us know! Vote for integrations on our GitHub feature requests Discuss integration needs with the community Email us about enterprise integration requirements Build custom integrations using our REST API ## Current Integrations While you wait for new integrations, check out what's already available: Visual workflow automation with 300+ app integrations No-code automation connecting 7,000+ apps Advanced automation scenarios with webhook triggers ## Build Your Own Don't see the integration you need? Our REST API makes it easy to build custom integrations: Explore our comprehensive API reference Create an API key in your HITL.sh dashboard Use your preferred programming language and HTTP client Contribute your integration back to the community Get started with our REST API documentation ## Stay Updated Follow our changelog for integration announcements Subscribe for updates on new integrations and features # Make Integration Source: https://docs.hitl.sh/integrations/make Build powerful visual automation scenarios with Make.com and HITL.sh, featuring real-time webhook triggers and advanced routing # Make Integration Integrate HITL.sh with Make.com (formerly Integromat) to create sophisticated automation scenarios with visual workflow design and real-time webhook triggers. Before configuring Make, install the HITL.sh mobile app ([App Store](https://apps.apple.com/us/app/hitl-human-in-the-loop/id6752878072) | [Google Play](https://play.google.com/store/apps/details?id=hitl.sh.app)) since it's an integral part of the setup. Make.com scenario with HITL.sh and Google Forms ## Why Use Make with HITL.sh? Design complex automation with an intuitive drag-and-drop visual interface Unique webhook trigger module for instant notifications when reviews complete Powerful data transformation and mapping between services Connect with Google Workspace, databases, CRMs, and countless other services ## Available Modules HITL.sh provides four powerful modules in Make.com: Submit content for human review: **Use Cases:** * Content moderation workflows * Approval processes * Quality assurance checks * Escalation routing **Required Fields:** * Loop ID * Request Text * Response Type * Response Config **Optional Fields:** * Processing Type * Priority * Timeout * Context * Callback URL Retrieve current status and response data: **Use Cases:** * Polling for completed reviews * Status monitoring * Progress tracking * Data synchronization **Required Fields:** * Request ID **Returns:** * Status (pending, completed, timeout, cancelled) * Response data * Reviewer information * Timestamps Direct access to HITL.sh API endpoints: **Use Cases:** * Advanced custom operations * Loop management * Bulk operations * Custom integrations **Features:** * Full API access * Custom headers * Query parameters * Request body customization Real-time trigger when requests complete: **Use Cases:** * Instant notification workflows * Real-time dashboards * Immediate action routing * Live status updates **Advantages:** * No polling required * Instant execution * Lower operation usage * Better performance This is a unique feature not available in other integration platforms! ## Module Comparison Choose the right module for your use case: | Feature | Create Request | Get Status | API Call | Watch Responses | | --------------------- | ----------------- | -------------- | ------------ | ------------------ | | **Submit Reviews** | ✅ | ❌ | ✅ | ❌ | | **Check Status** | ❌ | ✅ | ✅ | ✅ | | **Real-Time** | ❌ | ❌ | ❌ | ✅ | | **Custom Operations** | ❌ | ❌ | ✅ | ❌ | | **Ease of Use** | Easy | Easy | Advanced | Medium | | **Best For** | Creating requests | Status polling | Advanced use | Real-time triggers | ## Setting Up Authentication Connect your HITL.sh account to Make: Add any HITL.sh module to your scenario Click "Add" next to the Connection field Setting up HITL.sh connection in Make 1. Go to your [HITL.sh dashboard](https://my.hitl.sh) 2. Navigate to Settings → API Keys 3. Copy your API key 4. Paste it into the Make connection dialog Save the connection and test with a simple scenario ## Module Configuration ### Create Request Module Create Request module configuration in Make **Configuration Fields:** Your HITL loop identifier from the dashboard Content or question for reviewers to evaluate text | single\_select | multi\_select | rating | number | boolean JSON configuration for the response type time-sensitive (default) | deferred low | medium (default) | high | critical 60-86400 seconds (required for time-sensitive) Fallback value matching response type format Additional metadata as JSON object ### Get Request Status Module Get Request Status module configuration in Make **Configuration Fields:** The ID returned when creating a request **Returns:** ```json theme={null} { "request_id": "65f1234567890abcdef12348", "status": "completed", "priority": "high", "response_data": "approve", "response_by_user": { "id": "65f1234567890abcdef12350", "name": "John Reviewer", "email": "john@example.com" }, "response_time_seconds": 145.5, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:32:25Z" } ``` ### Make an API Call Module Make an API Call module for advanced operations **Use this for:** * Custom API operations * Loop management (create, update, delete) * Batch operations * Advanced queries **Example - List All Loops:** ``` URL: /api/loops Method: GET Headers: Authorization: Bearer {{API_KEY}} ``` **Example - Cancel Request:** ``` URL: /api/requests/{{request_id}} Method: DELETE Headers: Authorization: Bearer {{API_KEY}} ``` ### Watch Responses Module (Webhook Trigger) Watch Responses webhook trigger module **How it Works:** Add "HITL.sh > Watch Responses" as your scenario trigger Make generates a unique webhook URL for this scenario When creating requests, include the webhook URL as callback\_url Scenario automatically runs when reviewer completes the request **Webhook Payload:** ```json theme={null} { "event": "request.completed", "request_id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "status": "completed", "response_data": "approve", "response_by_user": { "id": "65f1234567890abcdef12350", "name": "Sarah Reviewer", "email": "sarah@example.com" }, "response_time_seconds": 127.5, "timestamp": "2024-03-15T10:32:25Z" } ``` ## Supported Response Types ### Text Response ```json theme={null} { "response_type": "text", "response_config": { "placeholder": "Enter feedback...", "min_length": 10, "max_length": 1000, "required": true } } ``` **Returns:** String value ### Single Select ```json theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Revise"] } } ``` **Returns:** Single string value (e.g., "approve") ### Multi Select ```json theme={null} { "response_type": "multi_select", "response_config": { "options": ["Accuracy", "Tone", "Grammar"], "max_selections": 3 } } ``` **Returns:** Array of strings (e.g., \["accuracy", "grammar"]) ### Rating ```json theme={null} { "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5 } } ``` **Returns:** Number value (e.g., 7.5) ### Number ```json theme={null} { "response_type": "number", "response_config": { "min_value": 0, "max_value": 1000, "decimal_places": 2 } } ``` **Returns:** Number value (e.g., 149.99) ### Boolean ```json theme={null} { "response_type": "boolean", "response_config": { "true_label": "Yes, Approved", "false_label": "No, Rejected" } } ``` **Returns:** Boolean value (true/false) ## Example Scenarios ### Google Forms Approval Workflow **Scenario Flow:** 1. **Trigger:** New Google Forms Response 2. **Filter:** Check if requires approval 3. **HITL Create Request:** Send for human review 4. **Router:** Route based on response * Approved → Update Google Sheet, Send email * Rejected → Archive response, Notify submitter * Escalate → Create ticket, Notify manager ### Real-Time Content Moderation **Scenario Flow (using Webhook):** 1. **Trigger:** HITL Watch Responses (Webhook) 2. **Switch:** Based on response\_data * "approve" → Post to social media, Log to database * "reject" → Send rejection notice, Archive content * "flag" → Create moderation ticket, Alert team ### Customer Support Escalation **Scenario Flow:** 1. **Trigger:** New Zendesk Ticket 2. **Filter:** Priority = High or Urgent 3. **HITL Create Request:** Route to support manager 4. **Wait for Response:** Poll status every 5 minutes 5. **Action:** Based on decision * Assign to specialist team * Escalate to executive * Resolve with template ## Advanced Techniques ### Data Transformation Map form data to HITL request format: ```javascript theme={null} // Set multiple variables { "loop_id": "{{1.loop_id}}", "request_text": formatString( "Review submission from {name}\nEmail: {email}\nMessage: {message}", {{1.name}}, {{1.email}}, {{1.message}} ), "priority": {{1.amount}} > 1000 ? "high" : "medium" } ``` ### Conditional Routing Use routers to handle different response types: ``` Router ├─ Path 1: response_data = "approve" │ └─ Send approval email │ └─ Update CRM │ └─ Post to Slack │ ├─ Path 2: response_data = "reject" │ └─ Send rejection notice │ └─ Archive submission │ └─ Path 3: response_data = "escalate" └─ Create high-priority ticket └─ Notify management └─ Schedule follow-up ``` ### Error Handling Add error handlers to your scenarios: Right-click any module → Add error handler Choose action: Ignore, Resume, Commit, or Rollback Send error notifications to your team ## Best Practices Prefer Watch Responses over polling Get Status for better performance Validate data before sending to HITL to prevent errors Always add error handlers to critical modules Run scenarios with test data before activating ## Troubleshooting **Solution:** * Verify API key is correct * Check API key permissions * Regenerate API key if needed * Test connection with simple request **Solution:** * Verify JSON syntax is correct * Match response\_config to response\_type * Check required fields are present * Review example configurations above **Solution:** * Verify webhook URL in callback\_url * Check webhook is active in Make * Test with manual webhook call * Review Make scenario execution logs **Solution:** * Increase timeout\_seconds value * Verify loop has active reviewers * Check notification settings * Consider using deferred processing ## Pricing & Operations Each module execution counts as one operation. Using webhooks (Watch Responses) is more efficient than polling (Get Status) as it only triggers when there's actual data. ## Next Steps Explore n8n for open-source automation Learn about Zapier for simpler workflows View complete API documentation Join Discord for scenario templates and support # n8n Integration Source: https://docs.hitl.sh/integrations/n8n Automate your human-in-the-loop workflows with n8n's powerful visual workflow builder and HITL.sh integration # n8n Integration Integrate HITL.sh with n8n to create powerful automated workflows that seamlessly incorporate human decision-making. Perfect for content moderation, approval workflows, and quality assurance processes. Before configuring n8n, install the HITL.sh mobile app ([App Store](https://apps.apple.com/us/app/hitl-human-in-the-loop/id6752878072) | [Google Play](https://play.google.com/store/apps/details?id=hitl.sh.app)) since it's an integral part of the setup. n8n workflow with HITL.sh integration ## Why Use n8n with HITL.sh? Design complex automation workflows with an intuitive drag-and-drop interface Connect HITL.sh with popular services like Slack, Gmail, databases, and more Deploy on your own infrastructure for complete data control and privacy Fully transparent codebase you can customize and extend to your needs ## Installation Methods n8n offers two ways to add HITL.sh integration to your workflows: ### Install via Community Nodes The easiest way to get started with HITL.sh in n8n: Navigate to **Settings** → **Community Nodes** in your n8n instance Search for `@hitlsh/n8n-nodes-hitl` and click **Install** Installing HITL.sh community node in n8n Restart your n8n instance to load the new nodes The HITL.sh nodes will now appear in your node palette Community Nodes are the recommended installation method as they receive automatic updates and simplified configuration. ### Install via NPM For self-hosted n8n instances with custom node installations: ```bash theme={null} # Navigate to your n8n custom nodes directory cd ~/.n8n/nodes # Install the HITL.sh node package npm install @hitlsh/n8n-nodes-hitl # Restart n8n n8n restart ``` **Directory Structure:** ``` ~/.n8n/ ├── nodes/ │ └── @hitlsh/ │ └── n8n-nodes-hitl/ └── config ``` Self-hosted NPM installation requires manual updates when new versions are released. ## Setting Up Credentials Configure your HITL.sh API credentials in n8n: 1. Log in to your HITL.sh dashboard at [my.hitl.sh](https://my.hitl.sh) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New API Key** 4. Copy your API key (keep it secure!) 1. In n8n, go to **Credentials** → **New** 2. Search for "HITL.sh" 3. Paste your API key Setting up HITL.sh API credentials in n8n Click **Test Credentials** to verify the connection ## Available Response Types HITL.sh n8n nodes support six different response types for collecting structured feedback from reviewers: Free-form text input with character limits: ```json theme={null} { "response_type": "text", "response_config": { "placeholder": "Enter your feedback...", "min_length": 10, "max_length": 500, "required": true } } ``` **Response format:** `"response_data": "The content looks good but needs minor edits..."` Choose one option from a predefined list: ```json theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Review"] } } ``` **Response format:** `"response_data": "approve"` Choose multiple options from a list: ```json theme={null} { "response_type": "multi_select", "response_config": { "options": ["Grammar Issues", "Factual Errors", "Tone Problems"], "max_selections": 3 } } ``` **Response format:** `"response_data": ["grammar_issues", "tone_problems"]` Numeric rating on a custom scale: ```json theme={null} { "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5 } } ``` **Response format:** `"response_data": 7.5` Numeric input with validation: ```json theme={null} { "response_type": "number", "response_config": { "min_value": 0, "max_value": 1000, "decimal_places": 2 } } ``` **Response format:** `"response_data": 299.99` Simple yes/no decisions: ```json theme={null} { "response_type": "boolean", "response_config": { "true_label": "Approved", "false_label": "Rejected" } } ``` **Response format:** `"response_data": true` ## Configuring the HITL.sh Node Add the HITL.sh node to your workflow and configure it: Configuring HITL.sh node parameters in n8n ### Required Parameters The unique identifier of your HITL loop where reviewers will process the request The content or question you want reviewers to evaluate Choose from: text, single\_select, multi\_select, rating, number, boolean Configuration object specific to your chosen response type ### Optional Parameters `time-sensitive` or `deferred` - determines urgency of the request `low`, `medium`, `high`, or `critical` - affects reviewer notification Time limit before default response is used (60-86400 seconds) Fallback value if request times out (format matches response type) Additional metadata to help reviewers make informed decisions ## Response Data Structure When a reviewer completes a request, you'll receive comprehensive response data: Complete response data structure from HITL.sh in n8n ```json theme={null} { "request_id": "65f1234567890abcdef12348", "loop_id": "65f1234567890abcdef12345", "status": "completed", "priority": "high", "response_data": "approve", "response_by_user": { "id": "65f1234567890abcdef12350", "name": "John Reviewer", "email": "john@example.com" }, "response_time_seconds": 145.5, "context": { "user_id": "user_123", "post_id": "post_456" }, "created_at": "2024-03-15T10:30:00Z", "updated_at": "2024-03-15T10:32:25Z" } ``` ## Example Workflow: Customer Refund Approval Here's a complete workflow that automatically processes refund requests with human oversight: n8n workflow for customer refund approvals with HITL.sh ### Workflow Steps Webhook or form submission triggers when customer requests refund Conditional node routes based on refund amount: * Under \$50: Auto-approve * $50-$500: Standard review * Over \$500: Priority review Send to human reviewer with refund details and customer history ```json theme={null} { "loop_id": "refund_approvals", "request_text": "Customer requesting ${{ $json.amount }} refund\nReason: {{ $json.reason }}\nCustomer lifetime value: ${{ $json.customer_ltv }}", "response_type": "single_select", "response_config": { "options": ["Approve Full", "Approve Partial", "Deny", "Escalate"] }, "priority": "{{ $json.amount > 500 ? 'high' : 'medium' }}", "context": { "customer_id": "{{ $json.customer_id }}", "order_id": "{{ $json.order_id }}", "amount": "{{ $json.amount }}" } } ``` Switch node routes based on human decision * **Approved**: Process refund and send confirmation * **Denied**: Send explanation email * **Escalate**: Notify management team Log decision in CRM/database for audit trail ## Best Practices Provide reviewers with all context needed to make informed decisions Set realistic timeouts based on request urgency and reviewer availability Always specify conservative default responses for timeout scenarios Add error nodes to handle API failures gracefully ## Troubleshooting **Solution**: * Verify installation completed successfully * Restart n8n instance * Check n8n logs for installation errors **Solution**: * Verify API key is correct * Check API key has required permissions * Ensure API key hasn't expired **Solution**: * Increase timeout\_seconds parameter * Verify loop has active reviewers * Check reviewer notification settings ## Next Steps Learn about our Zapier integration for simpler automation Explore Make.com integration with webhook triggers View complete API documentation for custom integrations Join our Discord community for help and examples # Zapier Integration Source: https://docs.hitl.sh/integrations/zapier Connect HITL.sh with 7,000+ apps using Zapier's no-code automation platform for seamless human-in-the-loop workflows # Zapier Integration Integrate HITL.sh with Zapier to connect human decision-making with over 7,000 apps and services. Perfect for teams who want powerful automation without writing code. Before configuring Zapier, install the HITL.sh mobile app ([App Store](https://apps.apple.com/us/app/hitl-human-in-the-loop/id6752878072) | [Google Play](https://play.google.com/store/apps/details?id=hitl.sh.app)) since it's an integral part of the setup. Zapier workflow with HITL.sh integration ## Why Use Zapier with HITL.sh? Connect HITL.sh with popular tools like Gmail, Slack, Google Sheets, Airtable, and more Build powerful workflows without writing a single line of code using visual interface Create complex automation sequences with conditional logic and multiple actions Get started in minutes with pre-built templates and intuitive configuration ## Available Actions HITL.sh provides two powerful actions in Zapier: ### Create Request Action Submit content for human review directly from your Zaps: HITL.sh action module in Zapier **Use Cases:** * Content moderation from form submissions * Approval workflows from CRM updates * Quality checks from data entries * Escalation from support tickets Search for "HITL.sh" and select "Create Request" action Connect your HITL.sh account using your API key Authenticating HITL.sh in Zapier Set up your request parameters Create Request - Setup phase in Zapier Create Request - Configuration in Zapier Test your Zap and turn it on to start automating ### Get Request Status Action Retrieve the status and response data of a submitted request: Get Request Status - Setup in Zapier Get Request Status - Configuration in Zapier **Use Cases:** * Poll for completed reviews * Update databases with reviewer decisions * Trigger follow-up actions based on responses * Generate reports from review outcomes The workflow waits for the human to answer before ending the execution using polling. ## Supported Response Types Configure how reviewers respond to your requests: Free-form text with character validation: **Configuration:** ```json theme={null} { "response_type": "text", "response_config": { "placeholder": "Enter feedback...", "min_length": 10, "max_length": 1000 } } ``` **Example Response:** `"The proposal looks solid but needs budget clarification in section 3."` Choose one option from a list: **Configuration:** ```json theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Revise"] } } ``` **Example Response:** `"approve"` Choose multiple options: **Configuration:** ```json theme={null} { "response_type": "multi_select", "response_config": { "options": ["Accuracy", "Tone", "Grammar", "Formatting"], "max_selections": 3 } } ``` **Example Response:** `["accuracy", "grammar"]` Numeric scale rating: **Configuration:** ```json theme={null} { "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 0.5 } } ``` **Example Response:** `4.5` Numeric input with validation: **Configuration:** ```json theme={null} { "response_type": "number", "response_config": { "min_value": 0, "max_value": 1000, "decimal_places": 2 } } ``` **Example Response:** `149.99` Simple yes/no decision: **Configuration:** ```json theme={null} { "response_type": "boolean", "response_config": { "true_label": "Approved", "false_label": "Rejected" } } ``` **Example Response:** `true` ## Request Configuration ### Required Fields The unique identifier of your HITL loop The content or question for reviewers Choose from: text, single\_select, multi\_select, rating, number, boolean Configuration object for your chosen response type ### Optional Fields `time-sensitive` (urgent) or `deferred` (non-urgent) Priority level: `low`, `medium`, `high`, or `critical` Time limit before default response is used (60-86400 seconds) Fallback value if request times out Additional metadata in JSON format ## Status Codes When retrieving request status, you'll receive one of four status codes: Waiting for reviewer response Reviewer has submitted response Request timed out, default response applied Request was cancelled before completion ## Example Workflows ### Content Approval from Google Forms Automatically send form submissions for human review: **Trigger:** New Google Forms Response **Action 1:** HITL.sh Create Request ``` Loop ID: content_review Request Text: {{Form Response}} Response Type: single_select Options: Approve, Reject, Needs Changes Priority: high ``` **Action 2:** Send Email (based on response) **Action 3:** Update Google Sheet with decision ### Slack Message Moderation Monitor Slack channels and flag inappropriate content: **Trigger:** New Message Posted in Slack **Filter:** Message contains flagged keywords **Action 1:** HITL.sh Create Request ``` Request Text: Review this Slack message: {{Message Text}} Response Type: single_select Options: Allow, Remove, Warn User Context: {"channel": "{{Channel}}", "user": "{{User ID}}"} ``` **Action 2:** Slack - Delete Message (if "Remove") **Action 3:** Slack - Send DM to User (if "Warn User") ### Customer Refund Workflow Handle refund requests with human oversight: **Trigger:** New Row in Airtable (Refund Requests) **Filter:** Amount > \$100 **Action 1:** HITL.sh Create Request ``` Request Text: Refund request for ${{Amount}} Reason: {{Reason}} Customer: {{Customer Name}} ({{Customer ID}}) Response Type: single_select Options: Approve Full, Approve Partial, Deny, Escalate Priority: {{Amount > 500 ? "high" : "medium"}} ``` **Action 2:** Update Airtable with decision **Action 3:** Send confirmation email **Action 4:** Process refund (if approved) ## Using Paths for Conditional Logic Zapier Paths allow you to handle different response types: ``` ├─ Path A: If response = "approve" │ └─ Send approval email │ └─ Update database status │ └─ Post to Slack channel │ ├─ Path B: If response = "reject" │ └─ Send rejection email │ └─ Archive submission │ └─ Log for review │ └─ Path C: If response = "escalate" └─ Notify management └─ Create high-priority ticket └─ Send urgent Slack message ``` ## Best Practices Always test your Zaps with sample data before going live Add filters to control when requests are created Set up error notifications to catch failed Zaps Track your Zapier task usage to stay within limits ## Troubleshooting **Solution:** * Verify API key is correct * Regenerate API key if needed * Check API key permissions in HITL dashboard **Solution:** * Confirm loop exists in your HITL dashboard * Copy Loop ID directly from loop settings * Check for extra spaces in the field **Solution:** * Check trigger conditions are met * Verify Zap is turned ON * Review Zap history for errors * Test trigger manually ## Pricing Considerations Zapier integration uses your Zapier task quota. Each action (Create Request, Get Status) counts as one task. The workflow uses polling to wait for human responses before ending execution. ## Next Steps Explore n8n for more advanced automation capabilities Learn about Make.com with webhook triggers View complete API documentation Browse Zapier templates shared by the community # Introduction to HITL.sh Source: https://docs.hitl.sh/introduction Learn about HITL.sh, the human-in-the-loop platform that bridges AI automation with human decision-making # Introduction to HITL.sh HITL.sh is a powerful human-in-the-loop platform that enables you to seamlessly integrate human decision-making into your AI workflows. Whether you're building content moderation systems, approval workflows, or AI training pipelines, HITL.sh provides the infrastructure to ensure human oversight where it matters most. ## What is Human-in-the-Loop? Human-in-the-loop (HITL) is an approach that combines AI automation with human expertise to create more reliable, accurate, and trustworthy systems. Instead of fully automated processes that might make errors, HITL systems route uncertain decisions to human reviewers who can apply judgment, context, and domain knowledge. * **Accuracy**: Human reviewers catch AI mistakes and edge cases * **Trust**: Build confidence in your AI systems with human oversight * **Compliance**: Meet regulatory requirements for human review * **Learning**: Improve AI models with human feedback and corrections ## How HITL.sh Works HITL.sh operates on a simple but powerful concept: Your AI system processes a request and either makes a decision or identifies uncertainty. When confidence is low or human oversight is required, HITL.sh routes the request to appropriate human reviewers. Reviewers examine the request using our intuitive mobile app or web interface and provide their decision. The human decision is sent back to your system, completing the loop and enabling your workflow to continue. ## Key Benefits Connect HITL.sh to any AI system via REST API, webhooks, or native integrations. Reviewers can respond to requests anywhere, anytime using our intuitive mobile app. Create custom approval chains, assign reviewers by expertise, and set up escalation rules. Get instant notifications and track the status of all requests in real-time. ## Use Cases HITL.sh is ideal for scenarios where human judgment adds value: * **Content Moderation**: Review flagged content, images, or videos * **Financial Approvals**: Validate transactions, loans, or expense reports * **Quality Assurance**: Verify AI-generated content, translations, or code * **Compliance Reviews**: Ensure regulatory requirements are met * **Training Data Validation**: Improve AI models with human feedback ## Getting Started Ready to integrate human oversight into your AI workflows? Follow our quick start guide to get up and running in minutes. Set up your first human-in-the-loop workflow in under 10 minutes. # Home Screen Source: https://docs.hitl.sh/mobile/home-screen Navigate the HITL.sh mobile app home screen to view pending requests and manage your review workload # Home Screen The HITL.sh mobile app home screen is your central hub for managing review requests on the go. Designed for efficiency and ease of use, it provides quick access to all the information you need to make informed decisions. ## Home Screen Overview HITL.sh mobile app home screen showing pending requests and navigation The home screen is organized into several key areas: * **Header Section**: Quick stats and navigation menu * **Request Queue**: List of pending requests requiring your review * **Quick Actions**: Common tasks and shortcuts * **Status Indicators**: Visual cues for request priority and status ## Header Section ### Profile and Stats * Your name and role * Current availability status * Skills and expertise areas * Performance metrics * Pending requests count * Today's completed reviews * Average response time * Quality score ### Navigation Menu Access all app features from the main menu: Return to the main dashboard with pending requests. View all requests assigned to you, including completed ones. See team performance and workload distribution. Configure notifications, preferences, and account settings. ## Request Queue ### Pending Requests List The main section displays all requests waiting for your review: Each request card shows: * **Content Preview**: First few words or image thumbnail * **Request Type**: Text, image, video, or document * **Priority Level**: Visual indicator (low, normal, high, urgent) * **Time Remaining**: Countdown to response deadline * **AI Confidence**: How confident the AI was in its analysis ### Request Prioritization Requests are automatically sorted by: Urgent requests appear at the top of the queue. Requests approaching their deadline are highlighted. Requests matching your skills are prioritized. First-in, first-out ordering for equal priority requests. ### Request Status Indicators Visual cues help you quickly understand request status: * **Green**: Low priority, normal response time * **Yellow**: Normal priority, approaching deadline * **Orange**: High priority, requires attention * **Red**: Urgent priority, immediate action needed * **Clock**: Request is waiting for review * **Alert**: Request is approaching deadline * **Star**: Request matches your expertise * **Flag**: Request has been flagged for special attention ## Quick Actions ### Review Actions Common actions available from the home screen: Begin reviewing a selected request immediately. Review multiple similar requests together. Save requests for review when you have more time. Get assistance from team members or supervisors. ### Filtering and Search Find specific requests quickly: Filter by text, image, video, or document requests. Show only high-priority or urgent requests. Search requests by content, user, or keywords. Filter requests by submission date or deadline. ## Request Details ### Quick Preview Tap on any request card to see more details: Mobile app showing request details with context and response options **Preview Information:** * **Full Content**: Complete text, image, or video * **AI Analysis**: Confidence scores and flagged issues * **Context**: User information, submission time, source * **History**: Previous similar requests and decisions ### Quick Decision Make decisions directly from the preview: Content passes review and can proceed. Content fails review and should be blocked. Request modifications before approval. Send to senior reviewer for additional review. ## Notifications ### Push Notifications Stay informed about new requests and deadlines: * **New Request**: New content assigned for review * **Deadline Alert**: Request approaching response time * **Escalation**: Request escalated to you * **Team Update**: Important team announcements ### Notification Settings Customize your notification preferences: Choose when to receive new request alerts. Set how far in advance to warn about deadlines. Receive notifications about team activities. Set times when notifications are silenced. ## Performance Tracking ### Personal Metrics Monitor your review performance: * Average time to complete reviews * Comparison with team averages * Improvement trends over time * Decision accuracy rating * Consistency with team decisions * Feedback from supervisors * Requests reviewed per day * Peak activity times * Workload distribution * New skills acquired * Training completed * Expertise areas expanded ### Team Insights View team performance and workload: Mobile app showing request history with completed, pending, and timed out requests **Team Metrics:** * **Overall Workload**: Total pending requests across the team * **Response Times**: Team average response times * **Quality Metrics**: Team decision consistency * **Workload Balance**: Distribution of requests across reviewers ### Request History Tabs Mobile app history screen showing completed requests View all successfully completed review requests with outcomes and feedback. Mobile app history screen showing timed out requests Track requests that exceeded their deadline and used default responses. ## Best Practices ### Efficient Reviewing Check the app regularly to maintain consistent response times. Focus on high-priority requests first to meet deadlines. Group similar requests for more efficient review. Use quick decision options for straightforward requests. ### Quality Assurance Take time to examine content carefully before deciding. Apply the same criteria across similar requests. Provide specific reasons for your decisions. Escalate complex requests when needed. ## Troubleshooting ### Common Issues * Check internet connection * Restart the app * Update to latest version * Clear app cache * Check notification permissions * Verify notification settings * Restart device * Reinstall app if needed * Pull to refresh the request list * Check internet connection * Log out and back in * Contact support if persistent ## Next Steps Ready to start reviewing requests on the go? Set up your account and join review loops. Learn how to provide effective responses to requests. Get the HITL.sh mobile app for iOS and Android. # How to Join a Loop Source: https://docs.hitl.sh/mobile/join-loop Step-by-step guide to joining review loops and setting up your account in the HITL.sh mobile app # How to Join a Loop Joining a loop in HITL.sh allows you to start reviewing requests and contributing to human-in-the-loop workflows. This guide walks you through the process of setting up your account and joining your first review loop. ## Prerequisites Before you can join a loop, you'll need: * ✅ An invitation email from a loop administrator * ✅ The HITL.sh mobile app installed on your device * ✅ A stable internet connection * ✅ Basic understanding of the review process Get the HITL.sh mobile app for iOS and Android devices. ## Step 1: Receive Invitation ### Invitation Email After downloading the app, you can join loops using multiple methods: HITL mobile app join loop screen with empty code input Enter a 6-digit loop code to join HITL mobile app join loop screen with filled code View loop details before joining **Join Methods Available:** * **Loop Code**: Enter a 6-digit code provided by the loop administrator * **QR Code Scan**: Use your camera to scan a QR code * **Direct Links**: Tap invitation links shared via email or messaging * **Social Sign-in**: Continue with Google, Apple, or email authentication Invitations typically expire after 7 days. If your invitation expires, contact the loop administrator for a new one. ### Alternative Invitation Methods Some loops may use different invitation methods: Click a shared link to join immediately. Scan a QR code with your mobile device. Enter a unique code in the mobile app. Administrator adds you directly to the loop. ## Step 2: Install and Open the App ### Download the App 1. Open the App Store on your iPhone or iPad 2. Search for "HITL.sh" 3. Tap "Get" or "Install" 4. Wait for the download to complete 1. Open the Google Play Store on your device 2. Search for "HITL.sh" 3. Tap "Install" 4. Wait for the download to complete ### First Launch Tap the HITL.sh icon on your home screen. Grant necessary permissions for notifications and internet access. Review the app introduction and tap "Get Started". ## Step 3: Accept the Invitation ### Using the Invitation Link Open the invitation email and tap the "Join Loop" button. The link will automatically open the HITL.sh mobile app. Review the loop details and tap "Accept Invitation". ### Using QR Code Scanning Scan a QR code to instantly join a loop: HITL mobile app QR code scanning interface Tap "Scan QR Code" from the join loop screen. Point your camera at the QR code provided by the loop administrator. The app will automatically detect and join the loop. ### Authentication Required Before joining loops, you'll need to authenticate: HITL mobile app get started screen with authentication options **Available Sign-in Methods:** * **Continue with Apple**: Use your Apple ID for quick authentication * **Continue with Google**: Sign in with your Google account * **Sign In With Your Email**: Use traditional email/password authentication ## Step 4: Complete Your Profile ### Basic Information Set up your reviewer profile: * **Full Name**: Your complete name as it should appear * **Email**: Your email address for notifications * **Phone**: Optional phone number for urgent alerts * **Profile Photo**: Upload a professional photo (optional) * **Primary Skills**: Your main areas of expertise * **Secondary Skills**: Additional skills and knowledge * **Experience Level**: Years of experience in relevant fields * **Languages**: Languages you can review content in * **Time Zone**: Your current time zone * **Working Hours**: When you're typically available * **Response Preferences**: How quickly you can respond * **Notification Settings**: How you want to be notified ### Skills Assessment Some loops may require a skills assessment: * **Content Moderation**: Review sample content for policy violations * **Quality Assurance**: Evaluate content for accuracy and completeness * **Compliance Review**: Assess documents for regulatory requirements * **Technical Review**: Evaluate technical content and code Skills assessments help ensure you're assigned appropriate requests and help you understand the review criteria. ## Step 5: Review Loop Guidelines ### Loop Overview Understand what you're joining: Mobile app showing loop details and membership information **Key Information:** * **Loop Purpose**: What types of content are reviewed * **Review Criteria**: Standards and guidelines for decisions * **Response Options**: Available decision choices * **Time Expectations**: Expected response times and deadlines ### Review Guidelines Study the specific guidelines for your loop: * What constitutes acceptable content * Common policy violations * Quality requirements * Cultural considerations * When to approve content * When to reject content * When to request changes * When to escalate issues * Required reasoning for decisions * Optional feedback fields * Escalation procedures * Communication guidelines * Sample approved content * Sample rejected content * Edge case scenarios * Common mistakes to avoid ## Step 6: Complete Training ### Training Modules Most loops include training materials: Read through all policy documents and guidelines. Complete sample reviews to test your understanding. Receive feedback on your practice reviews. Pass a final assessment to demonstrate readiness. ### Training Resources * **Video Tutorials**: Step-by-step review demonstrations * **Policy Documents**: Detailed guidelines and procedures * **FAQ Section**: Common questions and answers * **Contact Information**: How to get help when needed ## Step 7: Start Reviewing ### First Request Once you're approved, you'll receive your first request: Receive a push notification about a new request. Examine the content and context carefully. Choose the appropriate response option. Explain your decision with clear reasoning. Submit your decision to complete the review. ### Getting Help Don't hesitate to ask for assistance: Ask questions in the team discussion channel. Contact your supervisor for guidance. Reference the help section and guidelines. Escalate complex requests when needed. ## Troubleshooting ### Common Issues * Check if the invitation has expired * Ensure you're using the correct device * Try copying and pasting the link * Contact the loop administrator * Check device compatibility * Ensure sufficient storage space * Try restarting your device * Contact app store support * Check internet connection * Try refreshing the app * Clear app cache and restart * Contact technical support * Check internet connection * Try accessing from different network * Clear browser cache if using web version * Contact training administrator ## Best Practices ### Getting Started Don't rush through training - understanding is crucial. Clarify any unclear guidelines or procedures. Complete practice reviews to build confidence. Stick to established policies and procedures. ### Ongoing Success Keep up with policy changes and updates. Ask for feedback on your review quality. Use feedback to improve your decision-making. Support team members and share knowledge. ## Next Steps Congratulations! You're now part of a HITL.sh review loop. Here's what to do next: Navigate the app interface and view pending requests. Learn how to provide effective responses to requests. Study your loop's specific guidelines and policies. Introduce yourself and connect with fellow reviewers. # Overview Source: https://docs.hitl.sh/mobile/overview Complete guide to the HITL mobile app for reviewers. Learn how to join loops, respond to requests, and provide high-quality human feedback on the go. The HITL mobile app is where human reviewers receive and respond to your requests. Available on iOS and Android, it provides a streamlined interface for reviewing content, making decisions, and providing feedback in real-time. **For Reviewers**: This documentation helps reviewers understand how to use the mobile app effectively. Share this with your team members who will be responding to requests. ## Download the App Download HITL for iPhone and iPad from the App Store
**Requires**: iOS 14.0 or later
Download HITL for Android devices from Google Play
**Requires**: Android 8.0 (API level 26) or later
## Getting Started as a Reviewer HITL mobile app get started screen ### Step 1: Download and Install 1. Download the HITL app from your device's app store 2. Open the app and complete the initial setup 3. Create your reviewer account or sign in if you already have one ### Step 2: Join a Loop You can join loops in several ways: 1. Ask the loop creator for the QR code 2. Open the HITL app 3. Tap "Join Loop" on the main screen 4. Point your camera at the QR code 5. Confirm you want to join the loop 1. Get the 6-digit invite code from the loop creator 2. Open the HITL app 3. Tap "Join Loop" 4. Select "Enter Code Manually" 5. Type the invite code and tap "Join" 1. Tap the join link shared by the loop creator 2. The HITL app will open automatically 3. Review the loop details 4. Tap "Join Loop" to confirm ### Step 3: Set Up Notifications Enable push notifications to receive requests immediately: 1. Go to **Settings** in the app 2. Tap **Notifications** 3. Enable **Push Notifications** 4. Choose your notification preferences: * **New Requests**: Get notified of new requests in your loops * **Priority Requests**: Immediate alerts for high/critical priority items * **Loop Updates**: Notifications about loop changes ## App Interface Overview HITL mobile app home screen with active requests and loops ### Home Screen * **Active Requests**: Requests available for you to claim * **My Responses**: Requests you're currently working on * **Completed**: Recently completed requests * **Loops**: All loops you've joined ### Request List Each request shows: * **Priority Level**: Visual indicator (🟢 Low, 🟡 Medium, 🔴 High, ⚫ Critical) * **Request Preview**: First line of the request text * **Time Posted**: When the request was created * **Timeout**: Time remaining to complete the request * **Request Type**: Icon indicating the response type needed ### Loop Management * **Loop Details**: Name, description, and member count * **Loop Settings**: Notification preferences for each loop * **Leave Loop**: Option to leave loops you no longer want to participate in ## Responding to Requests ### Claiming a Request On the home screen, you'll see all unclaimed requests from your loops. Tap on a request that interests you or matches your expertise. Read the full request text and understand what's being asked. Tap "Claim Request" to start working on it. This prevents other reviewers from claiming the same request. Once you claim a request, you have a limited time to complete it (shown in the app). If you don't respond in time, the request may timeout and use the default response. ### Response Types in the App The mobile app provides optimized interfaces for each response type: **Interface**: Large text input area with character counter **Features**: * Rich text formatting (bold, italic, bullets) * Voice-to-text input support * Draft auto-save * Spell check and grammar suggestions **Best Practices**: * Be thorough but concise * Use proper grammar and spelling * Provide specific examples when possible * Stay objective and professional **Interface**: Radio button list with clear option descriptions **Features**: * Large, easy-to-tap buttons * Option descriptions that expand if needed * Search functionality for long lists * Confirmation before submitting **Best Practices**: * Read all options before selecting * Choose the most accurate option * If uncertain, look for "Other" or "Unsure" options * Don't guess if you don't know **Interface**: Checkbox list with selection counter **Features**: * Visual indication of how many items are selected * Min/max selection requirements clearly shown * Select all/clear all buttons for long lists * Search and filter options **Best Practices**: * Select all relevant options * Don't over-select to be "safe" * Pay attention to minimum/maximum requirements * Use "Other" option sparingly **Interface**: Star rating or slider control **Features**: * Visual feedback as you adjust the rating * Label descriptions for different rating levels * Half-star support where configured * Quick reset to change your rating **Best Practices**: * Use the full rating scale (don't just use 3-5) * Consider the rating labels provided * Be consistent with your rating criteria * Don't default to middle ratings **Interface**: Numeric keypad with unit labels **Features**: * Automatic input validation * Unit display (e.g., "items", "\$", "%") * Min/max value enforcement * Decimal precision control **Best Practices**: * Double-check your numbers * Pay attention to units and decimal places * Use estimation when exact values aren't available * Round appropriately for the context **Interface**: Large Yes/No or True/False buttons **Features**: * Clear visual distinction between options * Custom labels that describe the choice * Confirmation dialog for important decisions * Quick toggle interface **Best Practices**: * Read the labels carefully (not just Yes/No) * Be decisive - avoid changing your answer multiple times * Consider edge cases and default to the safer option * Ask for clarification if the choice isn't clear ## Quality Guidelines for Reviewers ### Response Quality Standards **Provide Correct Information** * Base responses on facts, not assumptions * Research when necessary and possible * Admit uncertainty rather than guessing * Double-check your work before submitting **Address All Requirements** * Read the entire request carefully * Answer all parts of multi-part questions * Provide sufficient detail for your response type * Include context when helpful **Respond Promptly** * Claim requests you can complete quickly * Don't claim requests if you can't finish them * Release unclaimed requests if your availability changes * Prioritize high-priority and time-sensitive requests **Apply Standards Uniformly** * Use similar criteria for similar requests * Maintain consistent rating scales * Follow any provided guidelines or examples * Ask for clarification on ambiguous requirements ### Writing Effective Text Responses **For Detailed Feedback**: 1. **Summary**: Brief overview of your assessment 2. **Specific Issues**: List problems you identified 3. **Recommendations**: Concrete suggestions for improvement 4. **Conclusion**: Final thoughts or overall rating explanation **Example**: ``` Summary: The article is informative but needs some improvements. Specific Issues: - Three factual errors about renewable energy statistics - Tone is too casual for the intended professional audience - Missing citations for key claims Recommendations: - Verify statistics with recent government reports - Revise language to be more formal and authoritative - Add proper citations and references Conclusion: With these changes, this could be an excellent piece for publication. ``` **Good**: "The pricing section has incorrect information. The basic plan is listed as $15/month but should be $19/month according to the current pricing page." **Poor**: "Some pricing stuff looks wrong." **Why Better**: Specific feedback tells the requester exactly what to fix and how to fix it. **Good**: "I recommend revising the introduction to better align with the target audience's technical level." **Poor**: "This intro is confusing and makes no sense." **Why Better**: Professional language focuses on improvement rather than criticism. **Good**: "Given that this content is intended for beginners, the technical jargon in paragraph 3 may be difficult to understand. Consider adding definitions or simplifying the language." **Poor**: "Too technical." **Why Better**: Explaining your reasoning helps the requester understand your perspective. ## Notification Management HITL mobile app push notification for new request ### Notification Types **When**: A new request is posted to one of your loops **Includes**: Loop name, request priority, and preview text **Action**: Tap to view and potentially claim the request **When**: High or critical priority requests are posted **Includes**: Urgency indicator and time-sensitive badge **Action**: Immediate notification with custom alert sound **When**: 15 minutes before your claimed request times out **Includes**: Time remaining and quick access to complete **Action**: Tap to quickly finish your response **When**: New members join, loop settings change, or announcements **Includes**: Loop name and summary of changes **Action**: Optional - can be disabled in settings ### Customizing Notifications HITL mobile app settings screen for notification customization 1. Go to **Settings** → **Notifications** 2. Configure notification preferences: * **Quiet Hours**: Set times when notifications are muted * **Priority Only**: Only receive critical/high priority alerts * **Loop-Specific**: Different settings for each loop * **Sound & Vibration**: Customize alert types * **Badge Count**: Show number of pending requests on app icon ## Performance and Reputation ### Quality Metrics The app tracks several quality indicators (visible to you and loop creators): **Measure**: Average time from claiming to completing requests **Good Range**: Varies by request type, typically 5-30 minutes **Impact**: Faster responses improve your reputation score **Measure**: Percentage of claimed requests you complete (vs. timeout) **Target**: Above 90% completion rate **Impact**: High completion rates increase request priority for you **Measure**: Ratings from request creators on your responses **Scale**: 1-5 stars with optional comments **Impact**: Higher ratings lead to more request opportunities **Measure**: How consistent your responses are with other reviewers **Assessment**: Automatic analysis of similar requests **Impact**: Consistency builds trust and reliability score ### Improving Your Performance * Only claim requests when you have sufficient time * Consider the complexity before claiming * Release requests if your availability changes * Prioritize requests that match your expertise * Read instructions carefully and follow them completely * Provide detailed feedback when requested * Use proper grammar and spelling * Stay objective and professional * Maintain regular activity in your loops * Apply consistent standards across similar requests * Communicate with loop creators about any issues * Update your availability status when needed * Review feedback on your responses * Ask loop creators for clarification on requirements * Observe how other reviewers handle similar requests * Adjust your approach based on performance metrics ## Troubleshooting Common Issues ### App Issues **Check**: 1. Notifications are enabled in phone settings for HITL app 2. App notification settings are configured correctly 3. Loop-specific notification settings are enabled 4. Phone is not in Do Not Disturb mode during active hours **Fix**: Go to Settings → Notifications → Reset to Default, then reconfigure **Possible Causes**: * Another reviewer claimed it first (requests are first-come, first-served) * You've reached your maximum concurrent request limit * Request has expired or been cancelled * Network connectivity issues **Fix**: Refresh the app, check your internet connection, or try claiming different requests **Quick Fixes**: 1. Force close and restart the app 2. Restart your phone 3. Check for app updates in the app store 4. Clear app cache (Android) or reinstall app (iOS) **If Issues Persist**: Contact support with your device model and app version **Common Solutions**: * Reset password using "Forgot Password" option * Check email for verification links * Ensure you're using the correct email address * Contact support if account was accidentally deactivated ### Request Issues **What to Do**: 1. Look for additional context in the request metadata 2. Check if there are similar completed requests for reference 3. Make your best professional judgment 4. Note the ambiguity in your response if using text input 5. Contact the loop creator if critically unclear **Best Practices**: * Don't guess on technical details you're unsure about * Focus on aspects you can evaluate (clarity, organization, etc.) * Indicate your confidence level in text responses * Consider releasing the request for someone more qualified **Immediate Actions**: 1. Do not ignore or automatically reject 2. Follow the specific reporting guidelines for the request type 3. Use available options like "Flag for Review" if present 4. Contact loop administrators for serious policy violations 5. Document your concerns in text responses when appropriate **Troubleshooting**: * Check internet connection * Ensure all required fields are completed * Verify response meets length/format requirements * Try force-closing and reopening the app * Check if request has timed out ## Best Practices for Loop Creators This section helps loop creators optimize their requests for better reviewer experience and response quality. ### Writing Clear Request Instructions **Good**: "Review this product description for accuracy. Check pricing, features, and availability claims against the information in the attached spec sheet." **Poor**: "Review this product description." **Why Better**: Specific instructions lead to more focused, useful responses. **Include**: * Purpose of the review (publication, internal use, compliance, etc.) * Target audience for the content being reviewed * Specific quality standards or guidelines to follow * Examples of good vs. poor responses when possible **Example**: "This article will be published on our company blog for potential customers. Please ensure the tone is professional but approachable, and verify all technical claims." * Use **text** for nuanced feedback requiring explanation * Use **single select** for clear binary decisions * Use **rating** for quality assessment that can be quantified * Use **multi select** for identifying multiple issues or features * Match the response type to the complexity of what you're asking * Simple yes/no decisions: 2-5 minutes * Rating with brief explanation: 5-10 minutes * Detailed text feedback: 10-30 minutes * Complex multi-part reviews: 30+ minutes Set timeouts generously to account for reviewer availability and quality expectations. ### Building Effective Review Teams * Match reviewer expertise to request types * Maintain teams of 3-8 active reviewers for availability * Consider time zones for time-sensitive requests * Include both experienced and newer reviewers for knowledge transfer * Share examples of high-quality responses * Clarify your quality standards and expectations * Provide feedback on reviewer performance regularly * Create guidelines documents for complex or technical reviews * Use the mobile app's loop features for communication * Share best practices among team members * Recognize high-performing reviewers * Address quality issues promptly and constructively ## Getting Help Access help articles and contact support directly from the mobile app settings. Contact [support@hitl.sh](mailto:support@hitl.sh) for account issues, technical problems, or feedback. Connect with other reviewers and loop creators in our community discussions. Watch step-by-step tutorials for common tasks and advanced features. ## Privacy and Security ### Data Protection * All request content is encrypted in transit and at rest * Personal information is protected according to GDPR and privacy standards * Reviewers only see content necessary for their specific requests * Response data is associated with reviewer IDs, not personal information ### Account Security * Use strong, unique passwords for your reviewer account * Enable two-factor authentication when available * Log out of shared devices * Report suspicious activity immediately ### Content Confidentiality * Treat all request content as confidential * Don't share request details outside the HITL platform * Don't screenshot or copy sensitive information * Follow any additional confidentiality agreements from loop creators *** ## Next Steps Ready to start reviewing? Here's how to get started with the HITL mobile app: Get the HITL mobile app for iOS and Android devices. Follow our step-by-step guide to join a review loop and start receiving requests. Learn how to use the app interface and manage your review workload. Discover how to provide effective responses across all response types. *** **Ready to start reviewing?** Download the HITL mobile app and join your first loop to begin providing valuable human feedback to AI systems and applications worldwide. # Responding to Requests Source: https://docs.hitl.sh/mobile/responding Complete guide to providing effective responses to review requests in the HITL.sh mobile app across all response types # Responding to Requests Providing quality responses is at the heart of the HITL.sh review process. This guide walks you through how to effectively respond to different types of review requests using the mobile app, ensuring your feedback is valuable and actionable. ## Getting Started with Responses ### Accessing Requests When you receive a new request, you'll be notified through the app: HITL mobile app push notification showing new review request **Ways to Access Requests:** * **Push Notifications**: Tap the notification to open the request directly * **Home Screen**: Select any request from your pending queue * **Request History**: Access previous requests from the history tab * **Direct Links**: Follow shared request links from team members ### Understanding Request Details Before responding, carefully review the request information: * **Request Type**: The kind of content you're reviewing * **Priority Level**: Urgency and importance indicators * **Context**: Background information and specific instructions * **Response Type**: How you should structure your response * **Deadline**: Time remaining to provide your response ## Response Types Overview HITL.sh supports six different response types, each optimized for specific review scenarios: Free-form detailed feedback and explanations Choose one option from predefined choices Select multiple relevant options Provide numeric ratings on customizable scales Enter specific numeric values with validation Make simple yes/no or true/false decisions ## Text Responses Perfect for detailed feedback, explanations, and qualitative assessments. ### Text Response Interface HITL mobile app text response interface with input field **Text Response Features:** * **Character Counter**: Shows remaining characters in real-time * **Placeholder Guidance**: Helpful hints about what to include * **Auto-Save**: Drafts are automatically saved as you type * **Formatting Support**: Basic text formatting options * **Length Validation**: Prevents submission outside required bounds ### Best Practices for Text Responses * Provide concrete examples and specific suggestions * Reference particular sections, elements, or issues * Include step-by-step recommendations where applicable * Avoid vague feedback like "needs improvement" * Use bullet points or numbered lists for clarity * Separate different types of feedback (grammar, content, style) * Lead with the most important points * End with a clear overall recommendation * Be constructive and helpful rather than critical * Use "I recommend" or "Consider" rather than "You must" * Acknowledge positive aspects before suggesting improvements * Keep feedback objective and focused on the content ### Example Text Responses ``` Article structure is clear and engaging. Grammar issues: - "it's" should be "its" in paragraph 2 - Missing comma after "However" in intro Content accuracy verified against sources. Recommend: 1. Add specific example in section 3 2. Strengthen conclusion with call-to-action 3. Consider adding subheadings for better readability Overall: Ready for publication with minor edits. Grade: B+ ``` ``` Security Analysis: ✅ Input validation implemented correctly ⚠️ Missing rate limiting on API endpoints ❌ SQL query vulnerable to injection (line 47) Performance: Good caching strategy, consider async processing for heavy operations. Recommendation: Fix security issues before deployment. Estimated effort: 4-6 hours. ``` ``` Design Review Results: - Layout: Responsive and mobile-friendly ✅ - Accessibility: Missing alt text on 3 images ⚠️ - Brand compliance: Colors match style guide ✅ - User experience: Clear navigation, intuitive flow ✅ Minor fixes needed for accessibility compliance. Otherwise approved. ``` ## Single Select Responses Ideal for clear decisions with mutually exclusive options. ### Single Select Interface HITL mobile app single select response with colored options **Single Select Features:** * **Visual Options**: Color-coded choices with icons and descriptions * **Clear Labels**: Descriptive text explaining each option * **Contextual Help**: Additional information for complex decisions * **Required Validation**: Prevents submission without selection ### When to Use Single Select Approve, reject, or request changes for content review processes. Classify content, issues, or requests into specific categories. Set priority levels, urgency indicators, or importance ratings. Update request status, progress indicators, or workflow stages. ### Single Select Best Practices Review every available option before making your selection to ensure you choose the most appropriate one. Factor in the specific request context, guidelines, and any special instructions provided. Pay attention to option descriptions and additional context to make informed decisions. Apply the same criteria across similar requests to maintain consistency in your reviews. ## Multi Select Responses Perfect when multiple aspects need to be evaluated simultaneously. ### Multi Select Interface HITL mobile app multi select response with checkboxes **Multi Select Features:** * **Checkbox Interface**: Clear selection indicators for each option * **Selection Limits**: Minimum and maximum selection requirements * **Category Grouping**: Options organized by relevant categories * **Selection Counter**: Shows current selections vs. requirements ### Multi Select Use Cases Select all problems, violations, or areas needing attention in the content being reviewed. Mark all regulatory requirements, policy guidelines, or standards that apply to the request. Evaluate multiple aspects like usability, design, functionality, and performance simultaneously. Check off all quality standards met or areas that need improvement in the submission. ### Multi Select Strategies Go through each option methodically, checking against the content or requirements. Don't rush through the list - take time to evaluate each criterion properly. Pay attention to minimum and maximum selection requirements. Some reviews may require you to select at least one option, while others may have upper limits on selections. ## Rating Responses Numeric ratings provide quantitative assessments on customizable scales. ### Rating Interface HITL mobile app rating response with slider interface **Rating Features:** * **Interactive Slider**: Smooth rating selection with precise control * **Scale Labels**: Custom labels for different rating levels * **Visual Feedback**: Real-time preview of selected rating * **Decimal Support**: Half-point or decimal ratings when configured ### Rating Guidelines Review the minimum and maximum values, and understand what each rating level represents before scoring. Apply the same standards across similar items to ensure your ratings are comparable and fair. Don't cluster ratings in the middle - use the full scale when content deserves high or low scores. Factor in the request's specific criteria and intended use case when determining ratings. ### Rating Scale Examples * **1-2**: Poor quality, major issues, not usable * **3-4**: Below average, significant problems * **5-6**: Average quality, minor issues * **7-8**: Good quality, meets standards well * **9-10**: Excellent, exceeds expectations * **1**: Very uncertain, need more information * **2**: Somewhat uncertain, limited confidence * **3**: Moderately confident in assessment * **4**: Quite confident, solid evaluation * **5**: Very confident, clear determination * **0-20**: Very low risk, minimal concerns * **21-40**: Low risk, minor considerations * **41-60**: Medium risk, moderate attention needed * **61-80**: High risk, significant concerns * **81-100**: Very high risk, immediate action required ## Number Responses Precise numeric input with validation and formatting options. ### Number Interface HITL mobile app number response with formatted input field **Number Features:** * **Formatted Input**: Automatic prefix, suffix, and decimal formatting * **Range Validation**: Prevents out-of-bounds number entry * **Input Assistance**: Numeric keypad optimized for mobile * **Real-time Formatting**: Shows formatted value as you type ### Number Response Applications Assess fair market prices, cost estimates, or value assessments for products, services, or assets. Count items, estimate amounts, or provide measurements for inventory, capacity, or resource planning. Provide specific performance indicators, scores, or measurements based on your expertise. Estimate completion times, durations, or scheduling requirements for tasks and projects. ### Number Input Tips Pay attention to decimal place requirements. Some requests need whole numbers, others require precise decimal values. Use appropriate precision for the context. Always double-check your number entries before submitting. Incorrect numeric values can have significant downstream impacts on pricing, scheduling, or resource allocation. ## Boolean Responses Simple binary decisions for clear yes/no scenarios. ### Boolean Interface Boolean responses present two clear options with custom labels and colors: **Boolean Features:** * **Clear Options**: Distinct true/false choices with descriptive labels * **Color Coding**: Visual indicators to reinforce the choice significance * **Quick Selection**: Fast decision-making for binary scenarios * **Confirmation**: Clear indication of selected option before submission ### Boolean Decision Making Consider the consequences of both options and choose the one that best serves the request's objectives. Use the same decision-making framework across similar boolean choices for consistency. When uncertain, choose the option that prioritizes safety, compliance, or conservative outcomes. Even for simple boolean choices, be prepared to explain your decision if questioned later. ### Boolean Use Cases "Does this content comply with regulations?" - Clear yes/no determination. "Does this meet quality standards?" - Pass/fail decisions for quality control. "Is this transaction suspicious?" - Binary risk determinations for security reviews. "Should this be approved?" - Simple approval/rejection decisions. ## Response Workflow ### Step-by-Step Response Process Read the request carefully, including context, instructions, and specific requirements for your response. Identify which type of response is expected and familiarize yourself with its specific requirements. Collect any additional information needed to provide an informed, accurate response. Compose your response thoughtfully, following best practices for the specific response type. Double-check your response for accuracy, completeness, and adherence to guidelines. Submit your response and verify it was recorded correctly in the system. ### Response Quality Checklist * ✅ Response addresses all aspects of the request * ✅ Information provided is factually correct * ✅ All required fields are completed properly * ✅ Response format matches requirements * ✅ Response is clear and easy to understand * ✅ Feedback is specific and actionable * ✅ Reasoning is explained when appropriate * ✅ Professional tone maintained throughout * ✅ Response submitted within deadline * ✅ Follows team guidelines and standards * ✅ Complies with relevant policies * ✅ Escalated when uncertain or appropriate ## Advanced Response Features ### Saving Drafts The mobile app automatically saves your progress: * **Real-time Saving**: Changes saved every few seconds * **Draft Recovery**: Resume interrupted responses after app restart * **Multiple Drafts**: Manage drafts for multiple concurrent requests * **Sync Across Devices**: Drafts available on all your devices ### Getting Help When you need assistance with a response: Ask the requestor for additional context or clarification when requirements are unclear. Consult with team members or supervisors for complex or unusual requests. Escalate requests that exceed your expertise or authority level. Reference guidelines, policies, and help materials while responding. ### Batch Responses For similar requests, use batch response features: Save frequently used responses as templates for similar future requests. Apply the same response to multiple similar requests when appropriate. Filter requests by type, priority, or similarity for efficient batch processing. ## Response Analytics ### Tracking Your Performance Monitor your response quality and efficiency: * Average time per response type * Improvement trends over time * Comparison with team benchmarks * Peak productivity periods * Response accuracy ratings * Consistency with team decisions * Feedback from requestors * Skill development progress * Responses completed per period * Request type distribution * Peak activity patterns * Workload balance * Decision implementation rates * Feedback utilization scores * Process improvement contributions * Value-added metrics ### Continuous Improvement Regularly review feedback on your responses to identify improvement opportunities. Study high-quality responses from experienced team members to enhance your skills. Work on maintaining consistent quality across different request types and contexts. Develop knowledge in new areas to handle a broader range of requests effectively. ## Troubleshooting ### Common Response Issues * Check internet connection stability * Verify all required fields are completed * Ensure response meets length/format requirements * Try refreshing the app and resubmitting * Confirm app has storage permissions * Check available device storage space * Update to latest app version * Clear app cache if problem persists * Re-read request instructions carefully * Check response type requirements * Look for examples or templates * Ask team members for clarification * Prioritize most critical aspects * Use quick response features when available * Request deadline extension if justified * Escalate if unable to complete quality response ### Performance Optimization * **Keyboard Shortcuts**: Learn app shortcuts for faster navigation * **Template Usage**: Create templates for common response patterns * **Batch Processing**: Group similar requests for efficient handling * **Focus Time**: Set aside dedicated time blocks for response work ## Best Practices Summary ### Quality Response Principles Review all aspects of the request and provide comprehensive, well-considered responses. Maintain professional objectivity and apply consistent standards across all responses. Ensure your responses are clear, specific, and actionable for the requestor. Respect time constraints while maintaining quality standards for all responses. ### Professional Development Continuously develop expertise in your review domains through training, practice, and learning from feedback. Prioritize response quality over quantity - thoughtful, accurate responses are more valuable than rushed ones. Engage with team members, share knowledge, and contribute to collective improvement of response processes. ## Next Steps Ready to start providing high-quality responses? Navigate the app interface and manage your request queue effectively. Get set up and start receiving requests to review and respond to. Deep dive into the technical details of all available response types. Get the HITL.sh mobile app and start responding to requests on the go. # Overview Source: https://docs.hitl.sh/overview Add human oversight to your AI workflows with our mobile-first platform. Create loops, send requests for human review, and receive structured responses via API. ## Overview Keep your AI systems safe and accurate with human oversight. HITL.sh provides a mobile-first platform for routing critical decisions to human reviewers with instant notifications and structured responses. Set up your first human-in-the-loop workflow in minutes with our API. ## How it works Set up a human review loop using the API. Each loop automatically generates invite codes and QR codes for easy team onboarding. Invite team members to join your loop using invite codes, QR codes, or direct links. They receive the mobile app for instant notifications. Submit review requests to your loop via API with customizable response types (single-select, text, rating, etc.) and timeout settings. Reviewers receive push notifications on mobile and can respond immediately with structured feedback. Receive human decisions via webhooks or polling, with structured data ready for your application logic. ## Core features Everything you need for human-in-the-loop workflows Reviewers receive instant push notifications and can respond on-the-go using our native mobile app for iOS and Android. Configure exactly how reviewers respond: single-select decisions, text feedback, ratings, numbers, or boolean choices. Invite codes, QR codes, and direct links make it simple to add reviewers to your loops instantly. Get instant webhooks when requests complete, or poll for status updates with structured JSON responses. ## Integrations Integrate human oversight into any application or workflow Full-featured REST API for creating loops, submitting requests, and receiving responses. Native n8n nodes for seamless workflow automation with human-in-the-loop steps. Connect HITL.sh to thousands of apps through Zapier's automation platform. Receive real-time notifications when human reviews are completed. Native iOS and Android apps for reviewers to handle requests on-the-go. Code examples and SDKs for popular programming languages. ## Use cases Real-world applications for human-in-the-loop workflows Review user-generated content for policy compliance with mobile-first moderation teams. Validate AI outputs before they reach customers, with structured feedback for model improvement. Route contracts, proposals, and sensitive documents through human approval workflows. Flag high-risk transactions, user accounts, or system anomalies for human review. ## Get started today Ready to add human oversight to your AI workflows? Follow our step-by-step guide to set up your first human-in-the-loop workflow with API examples. Browse our complete API documentation with code examples in Python, JavaScript, and cURL. Get the HITL.sh mobile app for iOS and Android to start reviewing requests on-the-go. Learn how to join existing loops using invite codes, QR codes, or direct links. # Content Types Source: https://docs.hitl.sh/requests/content-types Learn about the different content types supported for review requests # Content Types HITL.sh supports five content types for review requests. Each type determines how content is rendered on the mobile app and which fields are required when creating a request. Text-based content rendered as formatted markdown. No extra URL fields required. Visual content loaded from a URL. Requires `image_url`. Upload supported (10MB max). PDF, DOCX, and other files loaded from a URL. Requires `file_url`, `file_type`, and `file_name`. Upload supported (50MB max). YouTube, Vimeo, or raw video files loaded from a URL. Requires `video_url`. Link only — no upload. SoundCloud, Spotify, or raw audio files loaded from a URL. Requires `audio_url`. Link only — no upload. ## Comparison Table | Content Type | Field Required | Formats Supported | Upload | Max Size | | ------------ | ------------------------------------ | ------------------------------------------------- | ---------------- | -------- | | `markdown` | — | Markdown text | N/A | N/A | | `image` | `image_url` | JPG, PNG, GIF, WebP | Yes (public API) | 10MB | | `file` | `file_url`, `file_type`, `file_name` | PDF, DOCX, XLSX, and more | Yes (public API) | 50MB | | `video` | `video_url` | YouTube, Vimeo, .mp4, .webm, .mov | No (link only) | — | | `audio` | `audio_url` | SoundCloud, Spotify, .mp3, .wav, .ogg, .m4a, .aac | No (link only) | — | *** ## Markdown The `markdown` content type renders the `request_text` field as formatted markdown in the mobile app. Use it for text content such as comments, articles, support tickets, or any content where the full context can be expressed in text. ### Required Fields No additional URL fields are required beyond the standard request fields. ### API Example ```python theme={null} request_data = { "type": "markdown", "request_text": "## User Comment\n\nPlease review this comment for community guideline compliance:\n\n> 'This product changed my life! Use my code SAVE20 for a discount.'", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "escalate", "label": "🚨 Escalate", "color": "#8b5cf6"} ] }, "default_response": "reject" } ``` ### How It Renders * **Mobile app**: `request_text` is rendered as formatted markdown with support for headings, lists, bold/italic, code blocks, and links. * **Web portal**: Full markdown rendering with the same formatting support. *** ## Image The `image` content type loads a visual asset from a URL and displays it to the reviewer. Use it for profile photos, user-uploaded images, AI-generated graphics, or any content that requires visual inspection. ### Required Fields | Field | Type | Description | | ----------- | ------ | ----------------------- | | `image_url` | string | Public URL to the image | ### Supported Formats JPG, PNG, GIF, WebP, and other common image formats. The URL must be publicly accessible. ### API Example ```python theme={null} request_data = { "type": "image", "request_text": "Review this uploaded profile photo for appropriateness and compliance with our image guidelines.", "image_url": "https://cdn.example.com/uploads/profile_photos/user_12345.jpg", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "flag", "label": "🚩 Flag for Review", "color": "#f59e0b"} ] }, "default_response": "reject" } ``` ### How It Renders * **Mobile app**: Full-screen image viewer with pinch-to-zoom and pan support. The `request_text` appears as a header above the image. * **Web portal**: Inline image display with zoom capability. ### Upload Support You can upload images directly to HITL.sh (up to 10MB) rather than hosting them yourself. See the [Upload section](#uploading-files) below for details. *** ## Document The `file` content type loads a document from a URL and presents it to the reviewer. Use it for contracts, compliance documents, reports, invoices, or any file-based content requiring human review. ### Required Fields | Field | Type | Description | | ----------- | ------ | ---------------------------------- | | `file_url` | string | Public URL to the document | | `file_type` | string | MIME type (e.g. `application/pdf`) | | `file_name` | string | Display name shown to reviewers | ### Supported Formats PDF, DOCX, XLSX, PPTX, TXT, CSV, and other common document formats. The URL must be publicly accessible. ### API Example ```python theme={null} request_data = { "type": "file", "request_text": "Review this contract for compliance issues and confirm it meets our legal requirements.", "file_url": "https://cdn.example.com/documents/contract_v2.pdf", "file_type": "application/pdf", "file_name": "contract_v2.pdf", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "revise", "label": "✏️ Needs Revision", "color": "#f59e0b"} ] }, "default_response": "revise" } ``` ### How It Renders * **Mobile app**: Inline document viewer with scroll support. File name and type are shown as metadata above the viewer. * **Web portal**: Embedded document viewer. Reviewers can also open the file in a new tab. ### Upload Support You can upload documents directly to HITL.sh (up to 50MB) rather than hosting them yourself. See the [Upload section](#uploading-files) below for details. *** ## Video Link The `video` content type embeds a video player in the review interface. Use it for training videos, recorded demos, customer-submitted videos, or any video content requiring quality review. Video is link-only — HITL.sh does not support direct video file uploads. Host your video on YouTube, Vimeo, or a CDN and provide the URL. ### Required Fields | Field | Type | Description | | ----------- | ------ | ----------------------------------------------- | | `video_url` | string | URL to the video (platform or direct file link) | ### Supported Platforms and Formats * **Platforms**: YouTube, Vimeo * **Direct files**: `.mp4`, `.webm`, `.mov` hosted on a publicly accessible CDN ### API Example ```python theme={null} request_data = { "type": "video", "request_text": "Review this training video for accuracy and completeness before publishing to employees.", "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1, "labels": { "1": "Poor", "3": "Acceptable", "5": "Excellent" } }, "default_response": 3 } ``` ### How It Renders * **Mobile app**: Embedded video player with standard playback controls. The `request_text` appears above the player. * **Web portal**: Embedded iframe player for platform URLs, or HTML5 video player for direct file links. *** ## Audio Link The `audio` content type embeds an audio player in the review interface. Use it for call recordings, podcasts, voice samples, or any audio content requiring quality assurance review. Audio is link-only — HITL.sh does not support direct audio file uploads. Host your audio file on SoundCloud, Spotify, or a CDN and provide the URL. ### Required Fields | Field | Type | Description | | ----------- | ------ | ----------------------------------------------- | | `audio_url` | string | URL to the audio (platform or direct file link) | ### Supported Platforms and Formats * **Platforms**: SoundCloud, Spotify * **Direct files**: `.mp3`, `.wav`, `.ogg`, `.m4a`, `.aac` hosted on a publicly accessible CDN ### API Example ```python theme={null} request_data = { "type": "audio", "request_text": "Review this customer service call for quality assurance. Check for compliance with our communication guidelines.", "audio_url": "https://cdn.example.com/recordings/call_12345.mp3", "response_type": "single_select", "response_config": { "options": [ {"value": "satisfactory", "label": "✅ Satisfactory", "color": "#22c55e"}, {"value": "unsatisfactory", "label": "❌ Unsatisfactory", "color": "#ef4444"}, {"value": "escalate", "label": "🚨 Needs Escalation", "color": "#8b5cf6"} ] }, "default_response": "escalate" } ``` ### How It Renders * **Mobile app**: Embedded audio player with play/pause, seek bar, and playback speed controls. The `request_text` appears above the player. * **Web portal**: Embedded platform player for SoundCloud/Spotify URLs, or HTML5 audio player for direct file links. *** ## Uploading Files For image and document content types, HITL.sh provides a direct upload path so you do not need to manage your own file hosting. Upload images up to **10MB**. Supported formats: JPG, PNG, GIF, WebP. Upload documents up to **50MB**. Supported formats: PDF, DOCX, XLSX, PPTX, TXT, CSV. After uploading, HITL.sh returns a hosted URL that you pass as `image_url` or `file_url` in your request. Uploaded files are stored securely and served via CDN. Video and audio content types do not support direct uploads. Use a video hosting platform (YouTube, Vimeo) or a CDN for your media files and provide the URL in `video_url` or `audio_url`. *** ## Multiple Attachments You can attach multiple media items to a single request using array fields. This is useful for batch review, galleries, or requests requiring multiple supporting documents. | Array Field | Singular Field | Description | | ------------ | -------------- | ---------------------------------- | | `image_urls` | `image_url` | Multiple images for gallery review | | `file_urls` | `file_url` | Multiple documents | | `file_types` | `file_type` | MIME types for each file | | `file_names` | `file_name` | Display names for each file | | `video_urls` | `video_url` | Multiple videos | | `audio_urls` | `audio_url` | Multiple audio files | **Backward compatible:** Singular fields (`image_url`, `file_url`, etc.) continue to work exactly as before. Array fields are additive — use them only when you need to attach more than one item. ### API Example ```python theme={null} request_data = { "type": "image", "request_text": "Review these three product photos for quality and compliance before publishing to the store.", "image_urls": [ "https://cdn.example.com/products/item_42_front.jpg", "https://cdn.example.com/products/item_42_side.jpg", "https://cdn.example.com/products/item_42_detail.jpg" ], "response_type": "single_select", "response_config": { "options": [ {"value": "approve_all", "label": "✅ Approve All", "color": "#22c55e"}, {"value": "reject_some", "label": "⚠️ Reject Some", "color": "#f59e0b"}, {"value": "reject_all", "label": "❌ Reject All", "color": "#ef4444"} ] }, "default_response": "reject_all" } ``` ## Next Steps See complete API examples for all five content types. Full parameter reference for the create request endpoint. Learn how to collect structured feedback from reviewers. Understand how reviewers interact with each content type on mobile. # Requests Introduction Source: https://docs.hitl.sh/requests/introduction Learn about the different types of requests in HITL.sh and how they enable human-in-the-loop workflows # Requests Introduction Requests are the fundamental building blocks of HITL.sh workflows. They represent pieces of content, decisions, or actions that require human oversight before proceeding. Understanding how to create and structure requests is essential for building effective human-in-the-loop systems. ## What are Requests? A request in HITL.sh is a structured data package that contains: * **Content to Review**: The actual material requiring human oversight * **Context Information**: Background data to help reviewers make decisions * **AI Analysis**: Results from your AI system's initial processing * **Metadata**: Request details like priority, source, and timestamps * **Routing Instructions**: How the request should be handled - Flagged social media posts for content moderation - Suspicious financial transactions for fraud review - AI-generated content for quality assurance - Customer support tickets requiring escalation - Compliance documents for verification ## Request Lifecycle Requests follow a straightforward lifecycle from creation to completion: ### 1. Creation Your application creates a request using the API, specifying the content type, response requirements, and reviewer instructions: ```python theme={null} import requests # Create the request request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Review this comment: 'Great product! Use my referral code SAVE20.'", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"} ], "required": True }, "default_response": "reject", "timeout_seconds": 3600, "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) request_id = response.json()["data"]["request_id"] ``` ### 2. Broadcasting The API immediately sends push notifications to all active members of the target loop. Members receive the request in their mobile app with priority-based ordering. ### 3. Pending The request waits in the queue for a reviewer to respond. Requests are ordered by priority (critical → high → medium → low) and creation time. ### 4. Completed The reviewer submits their response using the configured response type. The request status changes to "completed" and the response data becomes available via the API. ### 5. Webhook (Optional) If you configured a `callback_url`, HITL.sh sends a webhook notification with the response data to your endpoint. ### Retrieving the Response ```python theme={null} # Poll for the completed response def get_response(request_id): response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": f"Bearer {api_key}"} ) if response.status_code == 200: data = response.json()["data"]["request"] if data["status"] == "completed": # Process the human response response_data = data["response_data"] if data["response_type"] == "single_select": decision = response_data["selected_value"] if decision == "approve": approve_content() elif decision == "reject": reject_content() elif data["status"] == "timeout": # Handle timeout using default response default_response = data["default_response"] handle_timeout_response(default_response) return data return None ``` ## Request Content Types HITL.sh supports five content types for human review: Review text-based content like comments, posts, articles, or documents formatted in markdown Review visual content like photos, graphics, or screenshots requiring human evaluation. Upload supported (10MB max) Review files such as PDFs, DOCX, and other documents via `file_url`, `file_type`, and `file_name`. Upload supported (50MB max) Review video content via `video_url`. Supports YouTube, Vimeo, or raw .mp4/.webm/.mov links. Link only — no upload Review audio content via `audio_url`. Supports SoundCloud, Spotify, or raw .mp3/.wav/.ogg/.m4a/.aac links. Link only — no upload ### Markdown Content Type Use `type: "markdown"` for text-based content review: ```python theme={null} request_data = { "type": "markdown", "request_text": "Please review this user comment: 'Great product! I've been using it for 6 months and highly recommend it.'", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"} ] } } ``` ### Image Content Type Use `type: "image"` for visual content review: ```python theme={null} request_data = { "type": "image", "request_text": "Review this uploaded profile photo for appropriateness", "image_url": "https://example.com/uploads/profile_123.jpg", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Appropriate"}, {"value": "reject", "label": "❌ Inappropriate"} ] }, "default_response": "reject" } ``` ### Document / File Content Type Use `type: "file"` for document review: ```python theme={null} request_data = { "type": "file", "request_text": "Review this contract for compliance issues", "file_url": "https://cdn.example.com/documents/contract_v2.pdf", "file_type": "application/pdf", "file_name": "contract_v2.pdf", "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "Approve"}, {"value": "reject", "label": "Reject"}, {"value": "revise", "label": "Needs Revision"} ] }, "default_response": "revise" } ``` ### Video Link Content Type Use `type: "video"` for video content review: ```python theme={null} request_data = { "type": "video", "request_text": "Review this training video for accuracy and completeness", "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1 }, "default_response": 3 } ``` ### Audio Link Content Type Use `type: "audio"` for audio content review: ```python theme={null} request_data = { "type": "audio", "request_text": "Review this customer service call for quality assurance", "audio_url": "https://cdn.example.com/recordings/call_12345.mp3", "response_type": "single_select", "response_config": { "options": [ {"value": "satisfactory", "label": "Satisfactory"}, {"value": "unsatisfactory", "label": "Unsatisfactory"}, {"value": "escalate", "label": "Needs Escalation"} ] }, "default_response": "escalate" } ``` ## Request Structure ### Required Fields Every request must include these essential elements: ```json theme={null} { "processing_type": "time-sensitive|deferred", "type": "markdown|image|file|video|audio", "priority": "low|medium|high|critical", "request_text": "string", "response_type": "text|single_select|multi_select|rating|number", "response_config": "object", "default_response": "varies by response_type", "platform": "api" } ``` ### Optional Fields Enhance requests with additional context and configuration: ```json theme={null} { "image_url": "https://example.com/image.jpg", "file_url": "https://example.com/document.pdf", "file_type": "application/pdf", "file_name": "document.pdf", "video_url": "https://youtube.com/watch?v=...", "audio_url": "https://soundcloud.com/artist/track", "context": { "user_id": "user_123", "post_id": "post_456", "automated_flags": ["potential_spam"], "previous_violations": 2 }, "timeout_seconds": 3600, "callback_url": "https://example.com/webhook/response", "platform": "api", "platform_version": "1.0.0" } ``` ## Request Priority Levels ### Low Priority Standard requests with no time sensitivity: * **Response Time**: 24-48 hours * **Reviewer Level**: Any available reviewer * **Examples**: General content moderation, routine quality checks ### Medium Priority Standard business requests: * **Response Time**: 4-8 hours * **Reviewer Level**: Standard reviewers * **Examples**: Content approval, user reports, policy reviews ### High Priority Time-sensitive requests requiring prompt attention: * **Response Time**: 1-2 hours * **Reviewer Level**: Experienced reviewers * **Examples**: Customer escalations, urgent compliance reviews ### Critical Priority Critical requests requiring immediate attention: * **Response Time**: 15-30 minutes * **Reviewer Level**: Senior reviewers or escalation team * **Examples**: Security incidents, legal compliance issues, emergency reviews ## Request States Track the progress of requests through their lifecycle: Request is waiting for a reviewer to respond. Human decision has been made and returned. Request exceeded response time and default response was used. Request was cancelled before completion by the creator. ## Creating Effective Requests ### Content Presentation Present content in a format that's easy for reviewers to understand. Include all information reviewers need to make informed decisions. Organize request data logically with consistent formatting. Set realistic priorities based on business impact and urgency. ### AI Analysis Integration Include AI confidence levels to help reviewers understand uncertainty. Highlight specific concerns the AI has identified. Provide risk scores and reasoning for human consideration. Include AI model version and processing details for transparency. ## Request Performance ### Metrics to Track Monitor request performance to optimize your workflows: * Average response time per request type * 95th percentile response times * Time to first response * Escalation frequency and timing * Decision consistency across reviewers * Inter-rater reliability scores * Error rates and types * Reviewer performance trends * Requests per day/week/month * Peak load times and patterns * Queue length and processing capacity * Loop utilization rates ### Optimization Strategies Group similar requests to reduce reviewer overhead. Route requests to reviewers with appropriate expertise. Distribute requests evenly across your reviewer team. Process high-priority requests before lower-priority ones. ## Best Practices ### Request Design Present content in a format that's easy for reviewers to understand. Include all information reviewers need to make informed decisions. Organize request data logically with consistent formatting. Set realistic priorities based on business impact and urgency. ### Performance Optimization * **Validation**: Verify request data before submission * **Consistency**: Maintain consistent request structure across loops * **Monitoring**: Track request processing times and success rates * **Feedback**: Use reviewer feedback to improve request quality ## Complete Examples ### Content Moderation (Markdown) Review text content for community guidelines: ```python theme={null} import requests # Create a markdown content review request request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Review this user comment for community guidelines: 'This product is amazing! Everyone should try it. Use code SAVE20 for discount.'", "context": { "user_id": "user_12345", "comment_id": "comment_789", "automated_flags": ["promotional_content"] }, "timeout_seconds": 1800, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "edit", "label": "✏️ Needs Editing", "color": "#f59e0b"} ], "required": True }, "default_response": "reject", "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ### Image Moderation Review visual content for appropriateness: ```python theme={null} # Create an image review request request_data = { "processing_type": "time-sensitive", "type": "image", "priority": "high", "request_text": "Review this uploaded profile photo for appropriateness and compliance with our image guidelines.", "image_url": "https://cdn.example.com/uploads/profile_photos/user_12345.jpg", "context": { "user_id": "user_12345", "upload_timestamp": "2024-01-15T10:30:00Z", "file_size": "2.4MB" }, "timeout_seconds": 900, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve Image", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject Image", "color": "#ef4444"}, {"value": "flag", "label": "🚩 Flag for Review", "color": "#f59e0b"} ], "required": True }, "default_response": "reject", # Conservative default "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ### Document Review (File) Review a file such as a contract or compliance document: ```python theme={null} import requests # Create a document review request request_data = { "processing_type": "deferred", "type": "file", "priority": "medium", "request_text": "Review this contract for compliance issues and confirm it meets our legal requirements.", "file_url": "https://cdn.example.com/documents/contract_v2.pdf", "file_type": "application/pdf", "file_name": "contract_v2.pdf", "context": { "contract_id": "contract_456", "client_name": "Acme Corp", "contract_value": "$50,000" }, "timeout_seconds": 86400, "response_type": "single_select", "response_config": { "options": [ {"value": "approve", "label": "✅ Approve", "color": "#22c55e"}, {"value": "reject", "label": "❌ Reject", "color": "#ef4444"}, {"value": "revise", "label": "✏️ Needs Revision", "color": "#f59e0b"} ], "required": True }, "default_response": "revise", "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ### Video Review Review a video for accuracy and quality: ```python theme={null} import requests # Create a video review request request_data = { "processing_type": "deferred", "type": "video", "priority": "low", "request_text": "Review this training video for accuracy and completeness before publishing to employees.", "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "context": { "video_id": "video_789", "department": "Engineering", "creator": "Learning & Development" }, "timeout_seconds": 86400, "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 1, "labels": { "1": "Poor", "3": "Acceptable", "5": "Excellent" }, "required": True }, "default_response": 3, "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ### Audio Review Review an audio recording for quality assurance: ```python theme={null} import requests # Create an audio review request request_data = { "processing_type": "time-sensitive", "type": "audio", "priority": "high", "request_text": "Review this customer service call for quality assurance. Check for compliance with our communication guidelines.", "audio_url": "https://cdn.example.com/recordings/call_12345.mp3", "context": { "call_id": "call_12345", "agent_id": "agent_99", "customer_tier": "enterprise", "call_duration_seconds": 342 }, "timeout_seconds": 3600, "response_type": "single_select", "response_config": { "options": [ {"value": "satisfactory", "label": "✅ Satisfactory", "color": "#22c55e"}, {"value": "unsatisfactory", "label": "❌ Unsatisfactory", "color": "#ef4444"}, {"value": "escalate", "label": "🚨 Needs Escalation", "color": "#8b5cf6"} ], "required": True }, "default_response": "escalate", "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ### Retrieving Responses ```python theme={null} def get_request_status(request_id): response = requests.get( f"https://api.hitl.sh/v1/api/requests/{request_id}", headers={"Authorization": f"Bearer {api_key}"} ) if response.status_code == 200: data = response.json()["data"]["request"] if data["status"] == "completed": return { "completed": True, "response_data": data["response_data"], "response_by": data["response_by_user"]["name"], "completion_time": data["completed_at"] } else: return { "completed": False, "status": data["status"], "timeout_at": data.get("timeout_at") } return None ``` ## Next Steps Ready to start creating requests for your loops? See the complete API documentation for creating requests with all parameters and examples. Learn about the 5 response types you can use to collect structured feedback from reviewers. Set up a loop with reviewers to process your requests. Understand how reviewers interact with your requests on the mobile app. # Multi Select Responses Source: https://docs.hitl.sh/responses/multi-select Comprehensive guide to implementing multi select responses for issue identification, categorization, and multiple decision workflows # Multi Select Responses Multi select responses allow reviewers to choose multiple options from a predefined list, making them ideal for identifying multiple issues, categorizing content across several dimensions, or conducting comprehensive audits where multiple aspects need evaluation. ## When to Use Multi Select Multi select responses are perfect for: Identifying multiple problems or violations where content might have several issues simultaneously Tagging content with multiple categories, departments, or attributes that aren't mutually exclusive Quality assurance workflows where multiple criteria need to be verified or flagged Evaluating multiple aspects or features of content, products, or submissions ## Configuration Formats Multi select responses support **two different formats** for the `options` array. Choose based on your needs: **Use string arrays** - Easiest approach for most use cases: ```json theme={null} { "response_type": "multi_select", "response_config": { "options": ["Grammar Issues", "Factual Errors", "Tone Problems"], "max_selections": 3 } } ``` **What happens:** * API automatically generates clean values: `"grammar_issues"`, `"factual_errors"`, `"tone_problems"` * Transformation: lowercase, spaces replaced with underscores * These generated values are returned in `response_data` as an array * Labels display to reviewers in the mobile app **Best for:** Quick setup, when you don't need custom value formats **Use objects with value/label** - Full control over returned values: ```json theme={null} { "response_type": "multi_select", "response_config": { "options": [ {"value": "pii", "label": "🔒 Contains Personal Information"}, {"value": "external_links", "label": "🔗 Includes External Links"}, {"value": "promo", "label": "📢 Has Promotional Content"} ], "max_selections": 3 } } ``` **What happens:** * You specify exact values to be returned: `["pii", "external_links", "promo"]` * Labels with emojis/descriptions display to reviewers * Full control over response data format * Perfect for matching database flag values **Best for:** Custom values, database flags, rich labels with emojis **Both formats work identically** - choose based on your preference. The API automatically transforms simple strings to rich objects for mobile app compatibility. Response data is always an array of selected values. ## Configuration Options ### Required Parameters Array of options (1-20 options maximum). Can be simple strings or SelectOption objects. ```json theme={null} ["Option 1", "Option 2", "Option 3"] ``` Automatically converted to values like `["option_1", "option_2"]` in response Internal value used in your application logic (max 100 characters) Display text shown to reviewers (max 200 characters) ### Optional Parameters Minimum number of options that must be selected (defaults to 1 when max\_selections is provided) Maximum number of options that can be selected (required for multi-select) Whether at least one selection is mandatory for completion ## Implementation Examples ### Content Violation Detection Identify multiple policy violations in a single review: ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Review this user post for policy violations:\n\n'🔥 AMAZING DEAL! Buy my crypto course for $999 (normally $5000)! DM me now before spots run out! This will make you RICH! Use my special link: bit.ly/crypto-riches. Limited time only - act fast or miss out forever! 💰💰💰'", "response_type": "multi_select", "response_config": { "options": [ { "value": "spam", "label": "🚫 Spam Content" }, { "value": "misleading_claims", "label": "⚠️ Misleading Claims" }, { "value": "financial_scam", "label": "💸 Potential Financial Scam" }, { "value": "suspicious_links", "label": "🔗 Suspicious Links" }, { "value": "pressure_tactics", "label": "⏰ High-Pressure Tactics" }, { "value": "no_violations", "label": "✅ No Policy Violations" } ], "min_selections": 1, "max_selections": 5, "required": True }, "default_response": ["spam", "misleading_claims"], # Conservative default "timeout_seconds": 1800, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "high", request_text: "Review this user post for policy violations:\n\n'🔥 AMAZING DEAL! Buy my crypto course for $999 (normally $5000)! DM me now before spots run out! This will make you RICH! Use my special link: bit.ly/crypto-riches. Limited time only - act fast or miss out forever! 💰💰💰'", response_type: "multi_select", response_config: { options: [ { value: "spam", label: "🚫 Spam Content" }, { value: "misleading_claims", label: "⚠️ Misleading Claims" }, { value: "financial_scam", label: "💸 Potential Financial Scam" }, { value: "suspicious_links", label: "🔗 Suspicious Links" }, { value: "pressure_tactics", label: "⏰ High-Pressure Tactics" }, { value: "no_violations", label: "✅ No Policy Violations" } ], min_selections: 1, max_selections: 5, required: true }, default_response: ["spam", "misleading_claims"], timeout_seconds: 1800, platform: "api" }; ``` ### Product Quality Assessment Evaluate multiple aspects of product quality: ```python Python theme={null} # Multi-dimensional product evaluation request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Evaluate this product listing for quality and completeness:\n\nTitle: 'Wireless Bluetooth Headphones - Premium Sound Quality'\nDescription: 'Experience crystal clear audio with our latest wireless headphones. Features noise cancellation, 20-hour battery life, and comfortable design.'\nPrice: $89.99\nImages: 3 product photos provided\nSpecifications: Listed in product details", "response_type": "multi_select", "response_config": { "options": [ { "value": "title_quality", "label": "📝 Title Quality Good" }, { "value": "description_complete", "label": "📄 Description Complete" }, { "value": "images_high_quality", "label": "📸 High-Quality Images" }, { "value": "pricing_competitive", "label": "💰 Competitive Pricing" }, { "value": "specifications_detailed", "label": "⚙️ Detailed Specifications" }, { "value": "category_correct", "label": "🏷️ Correct Category" }, { "value": "seo_optimized", "label": "🔍 SEO Optimized" } ], "min_selections": 1, "max_selections": 7, "required": True }, "default_response": [], "timeout_seconds": 86400, # 24 hours "platform": "api" } ``` ### Customer Service Ticket Categorization Categorize support tickets across multiple dimensions: ```python Python theme={null} # Multi-category ticket classification request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Categorize this customer support ticket:\n\n'Subject: Billing issue and account access problem\n\nMessage: Hi, I was charged twice for my subscription last month, and now I can't log into my account. I've tried resetting my password but the email isn't coming through. This is really frustrating and I need this resolved ASAP since I have a presentation tomorrow that requires access to my files. Can someone please help? I've been a customer for 3 years and this has never happened before.'", "response_type": "multi_select", "response_config": { "options": [ { "value": "billing_issue", "label": "💳 Billing & Payment" }, { "value": "account_access", "label": "🔐 Account Access" }, { "value": "technical_issue", "label": "⚙️ Technical Problem" }, { "value": "urgent_priority", "label": "🚨 Urgent Priority" }, { "value": "email_delivery", "label": "📧 Email Delivery Issue" }, { "value": "loyal_customer", "label": "⭐ Loyal Customer" }, { "value": "escalation_needed", "label": "📞 Needs Escalation" } ], "min_selections": 2, "max_selections": 6, "required": True }, "default_response": ["billing_issue", "account_access"], "timeout_seconds": 3600, "platform": "api" } ``` ## Response Format When a reviewer selects multiple options, you'll receive an array of the selected values: ```json theme={null} { "response_data": ["spam", "misleading_claims", "pressure_tactics"] } ``` ## Use Case Examples ### 1. Content Moderation Review ```python theme={null} moderation_config = { "response_type": "multi_select", "response_config": { "options": [ { "value": "harassment", "label": "👤 Harassment/Bullying", "description": "Targeted harassment or bullying behavior", "color": "#dc2626" }, { "value": "hate_speech", "label": "😡 Hate Speech", "description": "Content promoting hatred against groups", "color": "#991b1b" }, { "value": "misinformation", "label": "❌ Misinformation", "description": "False or misleading information", "color": "#f59e0b" }, { "value": "adult_content", "label": "🔞 Adult Content", "description": "Sexual or mature content", "color": "#7c2d12" }, { "value": "violence", "label": "⚔️ Violence/Graphic Content", "description": "Violent imagery or graphic content", "color": "#7f1d1d" }, { "value": "spam_promotion", "label": "📢 Spam/Promotion", "description": "Unsolicited advertising or spam", "color": "#ea580c" }, { "value": "copyright", "label": "©️ Copyright Violation", "description": "Unauthorized use of copyrighted material", "color": "#8b5cf6" }, { "value": "content_approved", "label": "✅ No Violations Found", "description": "Content appears to comply with all policies", "color": "#16a34a" } ], "min_selections": 1, "max_selections": 7, "required": True } } ``` ```python theme={null} def handle_moderation_decision(response_data): violations = response_data["selected_values"] labels = response_data["selected_labels"] # Check if content was approved if "content_approved" in violations: approve_content() log_decision("approved", labels) return # Process each violation type violation_actions = { "harassment": lambda: [remove_content(), warn_user(), log_violation("harassment")], "hate_speech": lambda: [remove_content(), suspend_user(days=7), log_violation("hate_speech")], "misinformation": lambda: [remove_content(), add_fact_check_label(), log_violation("misinformation")], "adult_content": lambda: [remove_content(), age_restrict_user(), log_violation("adult_content")], "violence": lambda: [remove_content(), escalate_to_safety_team(), log_violation("violence")], "spam_promotion": lambda: [remove_content(), limit_posting(), log_violation("spam")], "copyright": lambda: [remove_content(), notify_copyright_team(), log_violation("copyright")] } # Execute actions for each violation for violation in violations: if violation in violation_actions: actions = violation_actions[violation]() execute_actions(actions) # Determine overall severity severe_violations = ["hate_speech", "violence", "harassment"] if any(v in violations for v in severe_violations): escalate_to_senior_moderator() # Log complete decision log_moderation_decision(violations, labels) ``` ### 2. Quality Assurance Checklist ```python theme={null} qa_checklist_config = { "response_type": "multi_select", "response_config": { "options": [ { "value": "grammar_correct", "label": "📝 Grammar & Spelling Correct", "description": "No grammatical or spelling errors found", "color": "#059669" }, { "value": "facts_accurate", "label": "✅ Facts Verified", "description": "All factual claims have been verified", "color": "#0891b2" }, { "value": "sources_cited", "label": "📚 Sources Properly Cited", "description": "All sources are properly attributed", "color": "#7c3aed" }, { "value": "formatting_consistent", "label": "🎨 Formatting Consistent", "description": "Follows style guide and formatting standards", "color": "#2563eb" }, { "value": "images_optimized", "label": "🖼️ Images Optimized", "description": "Images are properly sized and compressed", "color": "#16a34a" }, { "value": "seo_compliant", "label": "🔍 SEO Best Practices", "description": "Follows SEO guidelines and best practices", "color": "#ea580c" }, { "value": "links_functional", "label": "🔗 All Links Working", "description": "All hyperlinks have been tested and work", "color": "#8b5cf6" }, { "value": "mobile_responsive", "label": "📱 Mobile Responsive", "description": "Content displays correctly on mobile devices", "color": "#f59e0b" } ], "min_selections": 0, "max_selections": 8, "required": False } } ``` ```python theme={null} def handle_qa_checklist(response_data): passed_checks = response_data["selected_values"] labels = response_data["selected_labels"] # Define all possible checks all_checks = [ "grammar_correct", "facts_accurate", "sources_cited", "formatting_consistent", "images_optimized", "seo_compliant", "links_functional", "mobile_responsive" ] # Calculate completion score completion_score = len(passed_checks) / len(all_checks) failed_checks = [check for check in all_checks if check not in passed_checks] # Determine next steps based on completion if completion_score >= 0.9: # 90%+ pass rate - approve for publication approve_for_publication() log_qa_result("approved", completion_score, passed_checks) elif completion_score >= 0.7: # 70-89% pass rate - minor revisions needed request_minor_revisions(failed_checks) set_status("revision_required") log_qa_result("minor_revisions", completion_score, failed_checks) elif completion_score >= 0.5: # 50-69% pass rate - major revisions needed request_major_revisions(failed_checks) set_status("major_revision_required") log_qa_result("major_revisions", completion_score, failed_checks) else: # <50% pass rate - reject and restart reject_submission() set_status("rejected") log_qa_result("rejected", completion_score, failed_checks) # Create specific action items create_action_items_for_failed_checks(failed_checks) # Notify stakeholders notify_qa_completion(completion_score, passed_checks, failed_checks) ``` ### 3. Feature Request Analysis ```python theme={null} feature_analysis_config = { "response_type": "multi_select", "response_config": { "options": [ { "value": "high_user_demand", "label": "📈 High User Demand" }, { "value": "strategic_value", "label": "🎯 Strategic Business Value" }, { "value": "technical_feasible", "label": "⚙️ Technically Feasible" }, { "value": "resource_available", "label": "👥 Resources Available" }, { "value": "competitive_advantage", "label": "🚀 Competitive Advantage" }, { "value": "low_maintenance", "label": "🔧 Low Maintenance" }, { "value": "security_compliant", "label": "🔒 Security Compliant" }, { "value": "scalability_ready", "label": "📊 Scalability Ready" } ], "min_selections": 1, "max_selections": 8, "required": True } } ``` ```python theme={null} def analyze_feature_request(response_data): positive_factors = response_data["selected_values"] labels = response_data["selected_labels"] # Weight different factors factor_weights = { "high_user_demand": 3, "strategic_value": 3, "technical_feasible": 2, "resource_available": 2, "competitive_advantage": 2, "low_maintenance": 1, "security_compliant": 2, "scalability_ready": 1 } # Calculate weighted score total_score = sum(factor_weights[factor] for factor in positive_factors) max_possible_score = sum(factor_weights.values()) priority_score = total_score / max_possible_score # Determine priority level if priority_score >= 0.8: priority = "P0 - Critical" next_action = "schedule_for_next_sprint" elif priority_score >= 0.6: priority = "P1 - High" next_action = "add_to_product_backlog_top" elif priority_score >= 0.4: priority = "P2 - Medium" next_action = "add_to_product_backlog" else: priority = "P3 - Low" next_action = "consider_for_future_roadmap" # Check for critical requirements must_haves = ["technical_feasible", "security_compliant"] missing_requirements = [req for req in must_haves if req not in positive_factors] if missing_requirements: priority = "BLOCKED" next_action = "address_blockers_first" # Log analysis results log_feature_analysis({ "priority_score": priority_score, "priority_level": priority, "positive_factors": positive_factors, "missing_requirements": missing_requirements, "recommended_action": next_action }) # Execute next action execute_feature_decision(next_action, positive_factors) return { "priority": priority, "score": priority_score, "factors": labels, "action": next_action } ``` ## Validation and Error Handling ### Automatic Validation The mobile app automatically validates multi select responses: * **Option validation**: Ensures all selected values exist in the options array * **Selection limits**: Enforces min\_selections and max\_selections constraints * **Required validation**: Prevents submission when required=true and no selections made * **Duplicate prevention**: Prevents selecting the same option multiple times ### Processing Validation Your application should validate received responses: ```python theme={null} def validate_multi_select_response(response_data, response_config): """Validate multi select response against configuration""" # Check response structure if not isinstance(response_data, dict): return False, "Response must be an object" if "selected_values" not in response_data: return False, "Missing selected_values field" selected_values = response_data["selected_values"] # Validate it's an array if not isinstance(selected_values, list): return False, "selected_values must be an array" # Validate all values exist in options valid_values = [opt["value"] for opt in response_config["options"]] invalid_values = [val for val in selected_values if val not in valid_values] if invalid_values: return False, f"Invalid selections: {invalid_values}" # Check selection limits min_selections = response_config.get("min_selections", 0) max_selections = response_config.get("max_selections", len(valid_values)) if len(selected_values) < min_selections: return False, f"Must select at least {min_selections} options" if len(selected_values) > max_selections: return False, f"Cannot select more than {max_selections} options" # Check required if response_config.get("required", False) and len(selected_values) == 0: return False, "At least one selection is required" # Check for duplicates if len(selected_values) != len(set(selected_values)): return False, "Duplicate selections not allowed" return True, "Valid" # Usage is_valid, error_message = validate_multi_select_response( response_data={ "selected_values": ["spam", "misleading_claims"], "selected_labels": ["🚫 Spam Content", "⚠️ Misleading Claims"] }, response_config={ "options": [...], "min_selections": 1, "max_selections": 5, "required": True } ) ``` ## Best Practices ### Option Design * Organize options by category or theme when possible * Use similar language patterns for related options * Consider visual grouping with colors for option categories * Order options from most to least common/important * Ensure options are mutually non-exclusive unless intended * Use descriptive labels that clearly differentiate choices * Include descriptions for options that might be ambiguous * Avoid overlapping categories that confuse reviewers * Set max\_selections to prevent analysis paralysis * Use min\_selections to ensure meaningful evaluation * Consider cognitive load - too many options reduce decision quality * Test limits with actual reviewers to find optimal ranges * Use colors strategically to indicate severity or category * Red/orange for problems, green for positive attributes * Consistent color coding across similar request types * Consider accessibility with color-blind friendly palettes ### Processing Best Practices ```python theme={null} # Weight different selections based on business impact selection_weights = { "critical_issue": 10, "major_issue": 5, "minor_issue": 1, "cosmetic_issue": 0.5 } def calculate_severity_score(selected_values): return sum(selection_weights.get(value, 0) for value in selected_values) ``` ```python theme={null} # Handle specific combinations of selections def process_selection_combinations(selected_values): if "urgent_issue" in selected_values and "customer_facing" in selected_values: escalate_immediately() if "billing_problem" in selected_values and "loyal_customer" in selected_values: prioritize_resolution() if set(["spam", "scam", "malicious"]).intersection(selected_values): trigger_security_review() ``` ```python theme={null} # Track selection patterns for insights def analyze_selection_patterns(responses): from collections import Counter import itertools # Most common individual selections all_selections = [] for response in responses: all_selections.extend(response["selected_values"]) common_selections = Counter(all_selections) # Most common selection combinations combinations = [] for response in responses: values = response["selected_values"] if len(values) >= 2: combinations.extend(itertools.combinations(sorted(values), 2)) common_combinations = Counter(combinations) return { "individual_frequencies": dict(common_selections), "combination_patterns": dict(common_combinations.most_common(10)) } ``` ## Common Patterns ### Issue Escalation Matrix ```python theme={null} # Escalate based on selection combinations escalation_rules = { ("security_threat", "customer_data"): "immediate_security_team", ("billing_error", "high_value_customer"): "senior_billing_specialist", ("technical_bug", "production_system"): "engineering_lead", ("content_violation", "repeat_offender"): "policy_enforcement_team" } def check_escalation_needed(selected_values): for combination, escalation_target in escalation_rules.items(): if all(item in selected_values for item in combination): return escalation_target return None ``` ### Quality Scoring System ```python theme={null} # Score content quality based on passed/failed criteria def calculate_quality_score(selected_criteria, all_possible_criteria): # Basic completion percentage completion_rate = len(selected_criteria) / len(all_possible_criteria) # Weight critical criteria more heavily critical_criteria = ["security_compliant", "legally_compliant", "factually_accurate"] critical_passed = len([c for c in selected_criteria if c in critical_criteria]) critical_total = len([c for c in all_possible_criteria if c in critical_criteria]) if critical_total > 0: critical_rate = critical_passed / critical_total # Heavily weight critical criteria (70% of score) final_score = (0.7 * critical_rate) + (0.3 * completion_rate) else: final_score = completion_rate return min(final_score, 1.0) # Cap at 1.0 ``` ## Next Steps Learn about numeric rating scales for quality assessment See how to implement mutually exclusive decision workflows Advanced patterns for handling and analyzing response combinations See how reviewers interact with multi select responses on mobile # Number Responses Source: https://docs.hitl.sh/responses/number Complete guide to implementing number responses for pricing, quantities, measurements, and numeric data collection with validation and formatting # Number Responses Number responses allow reviewers to input precise numeric values with customizable validation. They're perfect for collecting prices, quantities, measurements, scores, or any numeric data that requires accuracy and consistency. ## When to Use Number Responses Number responses are ideal for: Setting product prices, budget amounts, cost estimates, or any monetary values requiring precision Counting items, setting stock levels, capacity planning, or any discrete numeric quantities Recording dimensions, weights, distances, performance metrics, or scientific measurements Custom scoring systems, weighted calculations, or complex numeric evaluations beyond simple ratings ## Configuration Options Number responses support extensive validation options: ### Required Parameters Maximum allowed value (inclusive) ### Optional Parameters Minimum allowed value (inclusive, can be negative) Number of decimal places allowed (0-10) Whether negative numbers are permitted Whether a numeric value is mandatory for completion ## Implementation Examples ### Product Pricing Price input with validation: ```python Python theme={null} request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Please set the pricing for this new product based on the market analysis:\n\n**Product:** Premium Wireless Headphones\n**Cost to Manufacture:** $45.00\n**Market Research:**\n- Competitor A: $89.99\n- Competitor B: $129.99 \n- Competitor C: $79.99\n**Target Margin:** 40-60%\n**Recommended Price Range:** $75-$150\n\nConsider positioning, target market, and competitive landscape when setting the final price.", "response_type": "number", "response_config": { "min_value": 50.00, "max_value": 200.00, "decimal_places": 2, "allow_negative": False, "required": True }, "default_response": 89.99, # Conservative competitive pricing "timeout_seconds": 259200, # 3 days "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "deferred", type: "markdown", priority: "medium", request_text: "Please set the pricing for this new product based on the market analysis:\n\n**Product:** Premium Wireless Headphones\n**Cost to Manufacture:** $45.00\n**Market Research:**\n- Competitor A: $89.99\n- Competitor B: $129.99\n- Competitor C: $79.99\n**Target Margin:** 40-60%\n**Recommended Price Range:** $75-$150\n\nConsider positioning, target market, and competitive landscape when setting the final price.", response_type: "number", response_config: { min_value: 50.00, max_value: 200.00, decimal_places: 2, allow_negative: false, required: true }, default_response: 89.99, timeout_seconds: 259200, platform: "api" }; ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/{loop_id}/requests \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Please set the pricing for this new product...", "response_type": "number", "response_config": { "min_value": 50.00, "max_value": 200.00, "decimal_places": 2, "allow_negative": false, "required": true }, "default_response": 89.99, "timeout_seconds": 259200, "platform": "api" }' ``` ### Inventory Quantity Assessment Whole number inventory count with no decimals: ```python Python theme={null} # Inventory count verification request_data = { "processing_type": "time-sensitive", "type": "image", "image_url": "https://storage.company.com/warehouse/shelf-section-A7.jpg", "priority": "high", "request_text": "Count the number of 'Premium Coffee Blend' packages visible on this warehouse shelf section. This is for our quarterly inventory audit.\n\n**Product Details:**\n- SKU: PCB-001\n- Package Type: 12oz bags\n- Expected Range: 45-85 units\n- Location: Section A7, Shelf 3\n\nPlease provide an accurate count of visible packages.", "response_type": "number", "response_config": { "min_value": 0, "max_value": 200, "decimal_places": 0, # Whole numbers only "allow_negative": False, "required": True }, "default_response": 0, # Conservative default for inventory "timeout_seconds": 3600, # 1 hour "platform": "api" } ``` ```javascript Node.js theme={null} // Production capacity estimation const requestData = { processing_type: "deferred", type: "markdown", priority: "medium", request_text: "Based on the factory floor assessment, what is the realistic daily production capacity for our new assembly line?\n\n**Current Setup:**\n- 3 assembly stations\n- 2 shifts (16 hours total)\n- 4 workers per shift\n- Target: 15 minutes per unit\n\n**Factors to Consider:**\n- Break times and shift changes\n- Quality control checks\n- Equipment maintenance windows\n- Realistic worker efficiency\n\nPlease estimate daily units that can be consistently produced.", response_type: "number", response_config: { min_value: 20, max_value: 150, decimal_places: 0, allow_negative: false, required: true }, default_response: 64, // Conservative estimate based on 16 units/hour timeout_seconds: 86400, platform: "api" }; ``` ### Scientific Measurement High-precision measurement with decimals: ```python Python theme={null} # Laboratory measurement verification request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Verify the pH measurement reading from this laboratory test:\n\n**Sample:** Water Quality Test #WQ-2024-0847\n**Test Method:** Calibrated pH meter\n**Expected Range:** 6.5 - 8.5 (EPA drinking water standards)\n**Digital Reading:** 7.23\n**Lab Conditions:** 22°C, calibrated this morning\n\n**Quality Control:**\nPlease confirm the pH reading is accurate based on the sample color indicators and equipment calibration status shown in the attached documentation.\n\nNote: This is for regulatory compliance reporting.", "response_type": "number", "response_config": { "min_value": 0.0, "max_value": 14.0, "decimal_places": 2, "allow_negative": False, "required": True }, "default_response": 7.0, # Neutral pH default "timeout_seconds": 7200, # 2 hours "platform": "api" } ``` ### Budget Allocation Financial planning with negative values allowed: ```python Python theme={null} # Budget adjustment request request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Review the Q4 marketing budget variance and recommend adjustment:\n\n**Original Budget:** $50,000\n**Current Spend:** $42,500 (85% utilized)\n**Remaining Period:** 6 weeks\n**Performance:**\n- Lead generation: 120% of target\n- Conversion rate: 15% above average\n- Cost per acquisition: 12% below target\n\n**Options:**\n- Increase budget for additional campaigns (+)\n- Return unused budget to company (-)\n- Maintain current allocation (0)\n\nRecommend budget adjustment amount (positive = increase, negative = decrease, 0 = no change).", "response_type": "number", "response_config": { "min_value": -25000.00, "max_value": 25000.00, "decimal_places": 2, "allow_negative": True, "required": True }, "default_response": 0.00, # No change default "timeout_seconds": 604800, # 7 days "platform": "api" } ``` ## Response Format When a reviewer enters a number, you'll receive the numeric value: ```json theme={null} { "response_data": 129.99 } ``` ## Use Case Examples ### 1. Product Pricing Decision ```python theme={null} pricing_config = { "response_type": "number", "response_config": { "min_value": 10.00, "max_value": 500.00, "decimal_places": 2, "allow_negative": False, "required": True } } ``` ```json theme={null} { "response_data": 149.99 } ``` ```python theme={null} def process_pricing_decision(response_data, product_id): price = response_data # Get product cost data for margin calculation product_cost = get_product_cost(product_id) margin_dollars = price - product_cost margin_percentage = (margin_dollars / price) * 100 # Validate pricing strategy if margin_percentage < 20: flag_low_margin_warning(product_id, margin_percentage) elif margin_percentage > 70: flag_high_margin_review(product_id, margin_percentage) # Compare to competitor pricing competitor_prices = get_competitor_pricing(product_id) price_position = calculate_price_position(price, competitor_prices) # Update product pricing update_product_price(product_id, price) # Log pricing decision log_pricing_decision({ "product_id": product_id, "new_price": price, "margin_percentage": margin_percentage, "price_position": price_position, "decision_timestamp": datetime.utcnow() }) # Trigger related updates update_inventory_valuations(product_id, price) refresh_competitor_price_alerts(product_id) # Notify stakeholders based on price tier if price >= 200: notify_premium_pricing_team(product_id, price) elif margin_percentage < 25: notify_finance_team(product_id, margin_percentage) return { "price_set": price, "margin": f"{margin_percentage:.1f}%", "competitive_position": price_position } def calculate_price_position(price, competitor_prices): if not competitor_prices: return "no_competition_data" avg_competitor_price = sum(competitor_prices) / len(competitor_prices) if price <= min(competitor_prices): return "lowest_price" elif price >= max(competitor_prices): return "highest_price" elif price <= avg_competitor_price * 0.95: return "below_average" elif price >= avg_competitor_price * 1.05: return "above_average" else: return "competitive" ``` ### 2. Inventory Management ```python theme={null} inventory_config = { "response_type": "number", "response_config": { "min_value": 0, "max_value": 10000, "decimal_places": 0, "allow_negative": False, "required": True } } ``` ```json theme={null} { "response_data": 347 } ``` ```python theme={null} def process_inventory_count(response_data, sku, location): actual_count = response_data # Get expected count from system system_count = get_system_inventory_count(sku, location) variance = actual_count - system_count variance_percentage = (variance / system_count * 100) if system_count > 0 else 0 # Update inventory records update_inventory_count(sku, location, actual_count) # Handle significant variances if abs(variance_percentage) > 5: # More than 5% difference create_inventory_discrepancy_report({ "sku": sku, "location": location, "system_count": system_count, "actual_count": actual_count, "variance": variance, "variance_percentage": variance_percentage, "report_timestamp": datetime.utcnow() }) if abs(variance_percentage) > 20: # Major discrepancy escalate_to_inventory_manager(sku, variance_percentage) # Check reorder points reorder_point = get_reorder_point(sku) if actual_count <= reorder_point: trigger_reorder_process(sku, actual_count, reorder_point) # Update forecasting data update_demand_forecasting(sku, actual_count) # Log audit trail log_inventory_audit({ "sku": sku, "location": location, "counted_by": get_current_user_id(), "count": actual_count, "variance_from_system": variance, "audit_timestamp": datetime.utcnow() }) return { "count_recorded": actual_count, "variance": variance, "variance_percentage": f"{variance_percentage:+.1f}%", "reorder_needed": actual_count <= reorder_point } ``` ### 3. Performance Metrics ```python theme={null} performance_config = { "response_type": "number", "response_config": { "min_value": 0.0, "max_value": 100.0, "decimal_places": 1, "allow_negative": False, "required": True } } ``` ```json theme={null} { "response_data": 87.5 } ``` ```python theme={null} def process_performance_metric(response_data, employee_id, metric_type, review_period): performance_score = response_data # Store performance record performance_record = { "employee_id": employee_id, "metric_type": metric_type, "score": performance_score, "review_period": review_period, "recorded_timestamp": datetime.utcnow() } store_performance_record(performance_record) # Determine performance tier if performance_score >= 90: performance_tier = "exceptional" recommended_action = "promotion_consideration" elif performance_score >= 80: performance_tier = "exceeds_expectations" recommended_action = "merit_increase" elif performance_score >= 70: performance_tier = "meets_expectations" recommended_action = "maintain_current_role" elif performance_score >= 60: performance_tier = "needs_improvement" recommended_action = "development_plan" else: performance_tier = "unsatisfactory" recommended_action = "performance_improvement_plan" # Update employee performance profile update_performance_profile(employee_id, performance_score, performance_tier) # Generate recommendations if recommended_action in ["promotion_consideration", "merit_increase"]: schedule_recognition_review(employee_id, performance_score) elif recommended_action in ["development_plan", "performance_improvement_plan"]: create_improvement_plan(employee_id, performance_score, metric_type) # Update team performance metrics update_team_performance_dashboard(employee_id, performance_score) # Historical comparison historical_scores = get_historical_performance(employee_id, metric_type, limit=5) trend = calculate_performance_trend(historical_scores + [performance_score]) return { "performance_score": performance_score, "performance_tier": performance_tier, "recommended_action": recommended_action, "trend": trend, } def calculate_performance_trend(scores): if len(scores) < 2: return "insufficient_data" recent_avg = sum(scores[-3:]) / len(scores[-3:]) earlier_avg = sum(scores[:-3]) / len(scores[:-3]) if len(scores) > 3 else scores[0] if recent_avg > earlier_avg + 5: return "improving" elif recent_avg < earlier_avg - 5: return "declining" else: return "stable" ``` ## Validation and Error Handling ### Automatic Validation The mobile app automatically validates number responses: * **Type validation**: Ensures input is a valid numeric value * **Range validation**: Checks value falls within min\_value and max\_value bounds * **Decimal precision**: Enforces decimal\_places limit * **Required validation**: Prevents submission when required=true and no value provided * **Negative number handling**: Blocks negative values when allow\_negative=false ### Server-Side Validation Your application should validate received numbers: ```python theme={null} def validate_number_response(response_data, response_config): """Validate number response against configuration""" if not isinstance(response_data, (int, float)): return False, "Response must be a number" number = response_data # Validate numeric type (already done above) # number = response_data (simplified format) # Check range bounds min_value = response_config.get("min_value", 0) max_value = response_config["max_value"] if number < min_value: return False, f"Value must be at least {min_value}" if number > max_value: return False, f"Value cannot exceed {max_value}" # Check negative values allow_negative = response_config.get("allow_negative", False) if not allow_negative and number < 0: return False, "Negative values are not allowed" # Check decimal places decimal_places = response_config.get("decimal_places", 2) if decimal_places == 0 and number != int(number): return False, "Decimal values are not allowed" # Validate decimal precision decimal_str = str(number).split('.')[1] if '.' in str(number) else "" if len(decimal_str) > decimal_places: return False, f"Maximum {decimal_places} decimal places allowed" # Check required if response_config.get("required", False) and number is None: return False, "Number is required" return True, "Valid" # Usage example is_valid, error_message = validate_number_response( response_data=149.99, response_config={ "min_value": 50.00, "max_value": 500.00, "decimal_places": 2, "allow_negative": False, "required": True } ) ``` ## Best Practices ### Configuration Design * Use min\_value and max\_value to prevent obviously incorrect entries * Consider business context (e.g., product prices can't be $0.01 or $10,000) * Allow some buffer beyond expected range for edge cases * Test bounds with actual users to ensure they're not restrictive * **0 decimals**: For counting, whole quantities, percentages as integers * **2 decimals**: For monetary values, most business metrics * **3+ decimals**: For scientific measurements, precise calculations * Match precision to the business need and reviewer capability * Use descriptive request text to clarify what's being measured * Provide clear instructions about units or context in the request * Consider the context and units when appropriate * Always provide sensible default\_response values * Consider what happens with boundary values (min/max) * Plan for negative values when allow\_negative=true * Test with various input methods (typing, copy-paste) ### Processing Best Practices ```python theme={null} # Process numbers based on value ranges def process_by_range(value, thresholds): """Apply different logic based on numeric ranges""" if value <= thresholds["low"]: return handle_low_value(value) elif value <= thresholds["medium"]: return handle_medium_value(value) elif value <= thresholds["high"]: return handle_high_value(value) else: return handle_extreme_value(value) # Usage price_thresholds = {"low": 50, "medium": 100, "high": 200} inventory_thresholds = {"low": 10, "medium": 50, "high": 100} ``` ```python theme={null} # Handle floating point precision properly import decimal def process_financial_number(number_response): """Process financial numbers with proper precision""" # Convert to Decimal for precise financial calculations amount = decimal.Decimal(str(number_response)) # Round to appropriate precision rounded_amount = amount.quantize(decimal.Decimal('0.01')) return { "amount": float(rounded_amount), "cents": int(rounded_amount * 100) # For API integrations } ``` ```python theme={null} # Compare numbers against historical data def analyze_number_vs_history(current_value, historical_values): """Compare current number against historical patterns""" if not historical_values: return {"comparison": "no_history"} avg_historical = sum(historical_values) / len(historical_values) std_dev = calculate_std_deviation(historical_values) # Calculate z-score z_score = (current_value - avg_historical) / std_dev if std_dev > 0 else 0 # Determine significance if abs(z_score) > 2: significance = "highly_unusual" elif abs(z_score) > 1: significance = "unusual" else: significance = "normal" return { "current": current_value, "historical_average": avg_historical, "z_score": z_score, "significance": significance, "percentile": calculate_percentile(current_value, historical_values) } ``` ## Common Patterns ### Dynamic Range Validation ```python theme={null} # Adjust validation ranges based on context def get_dynamic_price_range(product_category, market_tier): """Calculate appropriate price ranges based on product context""" base_ranges = { "electronics": {"budget": (20, 200), "premium": (200, 2000)}, "clothing": {"budget": (10, 100), "premium": (100, 500)}, "home": {"budget": (25, 250), "premium": (250, 1000)} } if product_category in base_ranges: min_val, max_val = base_ranges[product_category][market_tier] return { "min_value": min_val, "max_value": max_val, "decimal_places": 2, } # Default fallback return { "min_value": 1.00, "max_value": 1000.00, "decimal_places": 2, } ``` ### Aggregate Calculations ```python theme={null} # Process multiple number responses together def calculate_weighted_average(number_responses, weights=None): """Calculate weighted average from multiple number responses""" if not number_responses: return None numbers = [response["number"] for response in number_responses] if weights and len(weights) == len(numbers): weighted_sum = sum(n * w for n, w in zip(numbers, weights)) total_weight = sum(weights) return weighted_sum / total_weight if total_weight > 0 else None else: # Simple average if no weights provided return sum(numbers) / len(numbers) ``` ## Next Steps Learn about simple true/false decision workflows Combine numeric data with detailed explanations Advanced techniques for validating and sanitizing numeric input See how reviewers interact with number inputs on mobile devices # Rating Responses Source: https://docs.hitl.sh/responses/rating Complete guide to implementing rating responses for quality assessment, performance evaluation, and scaled feedback collection # Rating Responses Rating responses allow reviewers to provide numeric assessments on customizable scales, making them ideal for quality evaluations, performance reviews, and any scenario where you need quantifiable feedback that can be easily aggregated and analyzed. ## When to Use Rating Responses Rating responses are perfect for: Evaluating content quality, product ratings, service assessments, or any subjective quality measurement Rating employee performance, AI model outputs, system effectiveness, or process efficiency Collecting feedback on user experience, satisfaction levels, or preference measurements Scoring risk levels, threat assessments, or priority ratings where numeric scales provide clarity ## Configuration Options Rating responses support flexible scale configuration with custom labels and increments: ### Required Parameters Maximum value on the rating scale ### Optional Parameters Minimum value on the rating scale (must be less than scale\_max) Step increment for the rating scale (e.g., 0.5 for half-star ratings, 1 for full-star ratings) Whether a rating is mandatory for completion ## Implementation Examples ### Content Quality Rating Five-star quality assessment with descriptive labels: ```python Python theme={null} request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Please rate the overall quality of this blog article:\n\n# '10 Essential Tips for Remote Work Productivity'\n\nWorking from home has become the new normal for millions of professionals worldwide. Whether you're a seasoned remote worker or just starting your work-from-home journey, these proven strategies will help you maintain peak productivity while enjoying the flexibility of remote work.\n\n## 1. Create a Dedicated Workspace\n\nDesignate a specific area in your home exclusively for work. This physical separation helps create mental boundaries between work and personal life...\n\n[Article continues with detailed tips and examples]", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 0.5, "required": True }, "default_response": 3, # Average rating if timeout "timeout_seconds": 86400, # 24 hours "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "deferred", type: "markdown", priority: "medium", request_text: "Please rate the overall quality of this blog article:\n\n# '10 Essential Tips for Remote Work Productivity'\n\nWorking from home has become the new normal for millions of professionals worldwide. Whether you're a seasoned remote worker or just starting your work-from-home journey, these proven strategies will help you maintain peak productivity while enjoying the flexibility of remote work.\n\n## 1. Create a Dedicated Workspace\n\nDesignate a specific area in your home exclusively for work. This physical separation helps create mental boundaries between work and personal life...\n\n[Article continues with detailed tips and examples]", response_type: "rating", response_config: { scale_min: 1, scale_max: 5, scale_step: 0.5, required: true }, default_response: 3, timeout_seconds: 86400, platform: "api" }; ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/{loop_id}/requests \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Please rate the overall quality of this blog article...", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 5, "scale_step": 0.5, "labels": { "1": "Poor - Major issues, needs complete rewrite", "2": "Below Average - Significant improvements needed", "3": "Average - Acceptable with minor edits", "4": "Good - High quality, minimal changes needed", "5": "Excellent - Ready to publish as-is" }, "required": true }, "default_response": 3, "timeout_seconds": 86400, "platform": "api" }' ``` ### Risk Assessment Scale Ten-point risk assessment with threshold labels: ```python Python theme={null} # Security threat risk assessment request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Assess the risk level of this security alert:\n\n**Alert Type:** Suspicious Login Activity\n**Details:** Multiple failed login attempts from IP 192.168.1.100 (Russia) targeting admin accounts\n**Time:** 15 attempts in the last 5 minutes\n**User Accounts:** admin@company.com, root@company.com, security@company.com\n**Additional Context:** These IPs have been flagged in threat intelligence feeds\n\nPlease rate the risk level from 1 (minimal) to 10 (critical threat).", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 1, "required": True }, "default_response": 8, # Conservative high-risk default "timeout_seconds": 900, # 15 minutes "platform": "api" } ``` ```javascript Node.js theme={null} // Performance evaluation rating const requestData = { processing_type: "deferred", type: "markdown", priority: "low", request_text: "Rate this employee's performance based on the quarterly review data:\n\n**Employee:** Sarah Johnson, Marketing Manager\n**Period:** Q4 2024\n**Key Achievements:**\n- Led successful product launch campaign (25% above target)\n- Improved team efficiency by implementing new workflows\n- Completed advanced marketing certification\n- Mentored 2 junior team members\n\n**Areas for Development:**\n- Could improve cross-departmental communication\n- Occasional delays in project deliverables\n\n**Team Feedback:** Consistently positive, described as collaborative and innovative\n\nPlease provide a performance rating from 1-10.", response_type: "rating", response_config: { scale_min: 1, scale_max: 10, scale_step: 0.5, required: true }, default_response: 5, timeout_seconds: 259200, // 3 days platform: "api" }; ``` ### User Experience Satisfaction Net Promoter Score (NPS) style rating: ```python Python theme={null} # Customer satisfaction survey request_data = { "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": "Based on this customer feedback, how likely would this customer be to recommend our service to others?\n\n**Customer Feedback:**\n'The onboarding process was smooth and the support team was incredibly helpful when I had questions. The product does exactly what I need it to do, and the pricing is fair. I've been using it for 6 months now and haven't had any major issues. The recent feature updates have made my workflow even more efficient. I'm quite satisfied overall.'\n\n**Usage Data:**\n- Customer for 6 months\n- Regular active user (4-5 times per week)\n- No support tickets for technical issues\n- Upgraded to premium plan after 3 months\n\nPlease rate on the NPS scale: 0-10 where 10 means extremely likely to recommend.", "response_type": "rating", "response_config": { "scale_min": 0, "scale_max": 10, "scale_step": 1, "required": True }, "default_response": 5, # Neutral default "timeout_seconds": 604800, # 7 days "platform": "api" } ``` ## Response Format When a reviewer provides a rating, you'll receive the numeric value: ```json theme={null} { "response_data": 4.5 } ``` ## Use Case Examples ### 1. Content Quality Evaluation ```python theme={null} quality_evaluation_config = { "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5, "required": True } } ``` ```json theme={null} { "response_data": 7.5 } ``` ```python theme={null} def process_quality_rating(response_data): rating = response_data["rating"] label = response_data["rating_label"] # Define quality thresholds if rating >= 8.5: # High quality - publish immediately approve_for_publication() set_priority("immediate_publish") log_quality_decision("approved_high_quality", rating) elif rating >= 6.5: # Good quality - minor edits needed request_copy_edit() set_status("minor_revision_needed") schedule_quick_review() log_quality_decision("approved_with_edits", rating) elif rating >= 4.0: # Average - needs improvement request_content_revision() set_status("major_revision_needed") schedule_resubmission_review() log_quality_decision("revision_required", rating) else: # Below acceptable - reject reject_content() set_status("rejected") request_complete_rewrite() log_quality_decision("rejected", rating) # Store rating for analytics store_quality_metrics(rating, label) # Update content creator stats update_creator_performance_metrics(rating) ``` ### 2. AI Model Performance Rating ```python theme={null} ai_performance_config = { "response_type": "rating", "response_config": { "scale_min": 0, "scale_max": 100, "scale_step": 5, "labels": { "0": "Completely Incorrect", "25": "Poor Accuracy", "50": "Average Performance", "75": "Good Accuracy", "90": "Excellent Performance", "100": "Perfect Accuracy" }, "required": True } } ``` ```json theme={null} { "response_data": { "rating": 85, "rating_label": "Good Accuracy" } } ``` ```python theme={null} def evaluate_ai_model_performance(response_data, model_id, test_case_id): accuracy_score = response_data["rating"] performance_label = response_data["rating_label"] # Store performance metrics performance_record = { "model_id": model_id, "test_case_id": test_case_id, "accuracy_score": accuracy_score, "performance_tier": get_performance_tier(accuracy_score), "evaluation_timestamp": datetime.utcnow(), "evaluator_feedback": performance_label } store_model_performance(performance_record) # Determine model status based on performance if accuracy_score >= 95: promote_to_production(model_id) notify_team("Model exceeds production threshold") elif accuracy_score >= 80: mark_for_production_consideration(model_id) schedule_additional_testing() elif accuracy_score >= 60: continue_training(model_id) flag_for_improvement() else: mark_for_major_revision(model_id) escalate_to_ml_team() # Update model metrics dashboard update_performance_dashboard(model_id, accuracy_score) # Trigger retraining if performance drops if accuracy_score < get_historical_average(model_id) - 10: trigger_model_retraining(model_id) def get_performance_tier(score): if score >= 90: return "Excellent" elif score >= 75: return "Good" elif score >= 60: return "Acceptable" else: return "Needs Improvement" ``` ### 3. Customer Satisfaction Survey ```python theme={null} satisfaction_survey_config = { "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 7, "scale_step": 1, "labels": { "1": "Extremely Dissatisfied", "2": "Dissatisfied", "3": "Somewhat Dissatisfied", "4": "Neutral", "5": "Somewhat Satisfied", "6": "Satisfied", "7": "Extremely Satisfied" }, "required": True } } ``` ```json theme={null} { "response_data": { "rating": 6, "rating_label": "Satisfied" } } ``` ```python theme={null} def process_satisfaction_rating(response_data, customer_id, interaction_id): satisfaction_score = response_data["rating"] satisfaction_label = response_data["rating_label"] # Categorize satisfaction level if satisfaction_score >= 6: satisfaction_tier = "promoter" follow_up_action = "request_testimonial" elif satisfaction_score >= 4: satisfaction_tier = "passive" follow_up_action = "improvement_survey" else: satisfaction_tier = "detractor" follow_up_action = "retention_outreach" # Store satisfaction data satisfaction_record = { "customer_id": customer_id, "interaction_id": interaction_id, "satisfaction_score": satisfaction_score, "satisfaction_tier": satisfaction_tier, "response_timestamp": datetime.utcnow(), "follow_up_scheduled": follow_up_action } store_satisfaction_data(satisfaction_record) # Trigger appropriate follow-up if satisfaction_tier == "promoter": # High satisfaction - request review or referral schedule_review_request(customer_id) add_to_referral_program(customer_id) elif satisfaction_tier == "detractor": # Low satisfaction - immediate intervention escalate_to_customer_success(customer_id, satisfaction_score) schedule_retention_call(customer_id) flag_for_service_recovery() # Update customer health score update_customer_health_metrics(customer_id, satisfaction_score) # Alert team if satisfaction trends negative recent_scores = get_recent_satisfaction_scores(customer_id, days=30) if len(recent_scores) >= 3 and all(score < 4 for score in recent_scores[-3:]): alert_account_manager(customer_id, "declining_satisfaction_trend") # Update overall satisfaction analytics update_satisfaction_dashboard(satisfaction_score, satisfaction_tier) ``` ## Validation and Error Handling ### Automatic Validation The mobile app automatically validates rating responses: * **Range validation**: Ensures rating falls within scale\_min and scale\_max bounds * **Step validation**: Verifies rating aligns with scale\_step increments * **Required validation**: Prevents submission when required=true and no rating provided * **Numeric validation**: Ensures only valid numeric values are accepted ### Server-Side Validation Your application should validate received ratings: ```python theme={null} def validate_rating_response(response_data, response_config): """Validate rating response against configuration""" if not isinstance(response_data, dict): return False, "Response must be an object" if "rating" not in response_data: return False, "Missing rating field" rating = response_data["rating"] # Validate numeric type if not isinstance(rating, (int, float)): return False, "Rating must be a number" # Check bounds scale_min = response_config["scale_min"] scale_max = response_config["scale_max"] if rating < scale_min or rating > scale_max: return False, f"Rating must be between {scale_min} and {scale_max}" # Check step alignment scale_step = response_config.get("scale_step", 1) if scale_step > 0: # Calculate if rating aligns with step steps_from_min = (rating - scale_min) / scale_step if not steps_from_min.is_integer(): return False, f"Rating must align with step increment of {scale_step}" # Check required if response_config.get("required", False) and rating is None: return False, "Rating is required" return True, "Valid" # Usage example is_valid, error_message = validate_rating_response( response_data={ "rating": 4.5, "rating_label": "Good - High quality" }, response_config={ "scale_min": 1, "scale_max": 5, "scale_step": 0.5, "required": True } ) ``` ## Best Practices ### Scale Design * **1-5 scale**: Best for simple quality assessments, easy to understand * **1-10 scale**: Good for detailed evaluations, allows more granularity * **0-100 scale**: Ideal for percentage-based ratings, performance metrics * **Custom ranges**: Use negative values for scales like -5 to +5 for sentiment * **Whole numbers (1.0)**: Simplest option, good for most use cases * **Half points (0.5)**: Adds precision without overwhelming complexity * **Decimal precision**: Use sparingly, mainly for calculated scores * **Larger steps (5)**: Good for percentage-based scales (0, 5, 10, 15...) * Always label the endpoints (minimum and maximum values) * Include middle anchor point for context * Add labels at natural breakpoints (quarters, thirds) * Use descriptive labels that explain the meaning, not just "poor/good" * Match scale complexity to reviewer expertise * Use familiar scales when possible (5-star, 1-10, percentage) * Consider cultural differences in rating interpretation * Test scales with actual users to ensure clarity ### Processing Best Practices ```python theme={null} # Define clear action thresholds thresholds = { "immediate_action": 9.0, # Exceptional - promote immediately "approve": 7.0, # Good - approve with minimal review "review_needed": 5.0, # Average - needs additional review "major_revision": 3.0, # Poor - significant work needed "reject": 1.0 # Unacceptable - reject } def determine_action(rating): for action, threshold in thresholds.items(): if rating >= threshold: return action return "reject" # Default for ratings below all thresholds ``` ```python theme={null} # Combine multiple ratings intelligently def aggregate_ratings(ratings, method="weighted_average"): if method == "simple_average": return sum(ratings) / len(ratings) elif method == "weighted_average": # Weight more recent ratings higher weights = [1.0 + (i * 0.1) for i in range(len(ratings))] weighted_sum = sum(r * w for r, w in zip(ratings, weights)) return weighted_sum / sum(weights) elif method == "median": sorted_ratings = sorted(ratings) n = len(sorted_ratings) return sorted_ratings[n//2] if n % 2 else (sorted_ratings[n//2-1] + sorted_ratings[n//2]) / 2 elif method == "consensus": # Remove outliers and average remaining if len(ratings) >= 5: sorted_ratings = sorted(ratings) # Remove top and bottom 20% trimmed = sorted_ratings[1:-1] if len(ratings) >= 5 else ratings return sum(trimmed) / len(trimmed) else: return sum(ratings) / len(ratings) ``` ```python theme={null} # Track rating trends over time def analyze_rating_trends(entity_id, time_period_days=30): ratings = get_ratings_for_period(entity_id, time_period_days) if len(ratings) < 3: return {"trend": "insufficient_data"} # Calculate trend direction recent_avg = sum(ratings[-3:]) / 3 earlier_avg = sum(ratings[:-3]) / len(ratings[:-3]) if len(ratings) > 3 else recent_avg trend_direction = "improving" if recent_avg > earlier_avg + 0.3 else \ "declining" if recent_avg < earlier_avg - 0.3 else \ "stable" return { "trend": trend_direction, "current_average": recent_avg, "overall_average": sum(ratings) / len(ratings), "rating_count": len(ratings), "volatility": calculate_rating_volatility(ratings) } def calculate_rating_volatility(ratings): if len(ratings) < 2: return 0 avg = sum(ratings) / len(ratings) variance = sum((r - avg) ** 2 for r in ratings) / len(ratings) return variance ** 0.5 # Standard deviation ``` ## Analytics and Reporting ### Rating Distribution Analysis ```python theme={null} def analyze_rating_distribution(ratings): """Analyze patterns in rating data""" from collections import Counter import statistics if not ratings: return {"error": "No ratings to analyze"} # Basic statistics stats = { "count": len(ratings), "mean": statistics.mean(ratings), "median": statistics.median(ratings), "mode": statistics.mode(ratings) if len(set(ratings)) < len(ratings) else None, "std_dev": statistics.stdev(ratings) if len(ratings) > 1 else 0, "min": min(ratings), "max": max(ratings) } # Distribution analysis rating_counts = Counter(ratings) total_ratings = len(ratings) distribution = {} for rating, count in rating_counts.items(): percentage = (count / total_ratings) * 100 distribution[str(rating)] = { "count": count, "percentage": round(percentage, 1) } # Identify patterns patterns = { "central_tendency": "low" if stats["mean"] < 3 else "high" if stats["mean"] > 7 else "middle", "variability": "low" if stats["std_dev"] < 1 else "high" if stats["std_dev"] > 2 else "moderate", "most_common_rating": max(rating_counts.items(), key=lambda x: x[1])[0] } return { "statistics": stats, "distribution": distribution, "patterns": patterns } ``` ### Performance Benchmarking ```python theme={null} def benchmark_against_category(rating, category_id): """Compare individual rating against category benchmarks""" # Get category statistics category_stats = get_category_rating_stats(category_id) if not category_stats: return {"error": "No benchmark data available"} # Calculate percentile percentile = calculate_percentile(rating, category_stats["all_ratings"]) # Determine performance tier if percentile >= 90: performance_tier = "Top 10%" elif percentile >= 75: performance_tier = "Above Average" elif percentile >= 25: performance_tier = "Average" else: performance_tier = "Below Average" return { "rating": rating, "category_average": category_stats["mean"], "percentile": percentile, "performance_tier": performance_tier, "above_average": rating > category_stats["mean"] } def calculate_percentile(value, data_set): """Calculate what percentile a value represents in a dataset""" below_value = sum(1 for x in data_set if x < value) return (below_value / len(data_set)) * 100 ``` ## Next Steps Learn about numeric input with validation and formatting Implement simple true/false decision workflows Advanced techniques for analyzing and aggregating rating data See how reviewers interact with rating scales on mobile devices # Single Select Responses Source: https://docs.hitl.sh/responses/single-select Complete guide to implementing single select responses for structured decision-making with predefined options, colors, and descriptions # Single Select Responses Single select responses allow reviewers to choose exactly one option from a predefined list, making them perfect for clear decision workflows, approval processes, and structured categorization where mutual exclusivity is important. ## When to Use Single Select Single select responses are ideal for: Simple approve/reject decisions, or approval with conditions like "Approve", "Reject", "Needs Changes" Categorizing content into mutually exclusive categories like content type, priority level, or department Rating content quality with discrete levels like "Excellent", "Good", "Fair", "Poor" Setting request status, priority levels, or routing decisions where only one choice makes sense ## Configuration Formats Single select responses support **two different formats** for the `options` array. Choose based on your needs: **Use string arrays** - Easiest approach for most use cases: ```json theme={null} { "response_type": "single_select", "response_config": { "options": ["Approve", "Reject", "Needs Review"] } } ``` **What happens:** * API automatically generates clean values: `"approve"`, `"reject"`, `"needs_review"` * Transformation: lowercase, spaces replaced with underscores * These generated values are returned in `response_data` * Labels display to reviewers in the mobile app **Best for:** Quick setup, when you don't need custom value formats **Use objects with value/label** - Full control over returned values: ```json theme={null} { "response_type": "single_select", "response_config": { "options": [ {"value": "approved", "label": "✅ Approve - Safe to publish"}, {"value": "rejected", "label": "❌ Reject - Violates guidelines"}, {"value": "review", "label": "⚠️ Needs Review - Unclear content"} ] } } ``` **What happens:** * You specify exact values to be returned: `"approved"`, `"rejected"`, `"review"` * Labels with emojis/descriptions display to reviewers * Full control over response data format * Perfect for matching database enum values **Best for:** Custom values, database keys, rich labels with emojis **Both formats work identically** - choose based on your preference. The API automatically transforms simple strings to rich objects for mobile app compatibility. ## Configuration Options ### Required Parameters Array of options (1-20 options maximum). Can be simple strings or SelectOption objects. ```json theme={null} ["Option 1", "Option 2", "Option 3"] ``` Automatically converted to values like `"option_1"`, `"option_2"`, `"option_3"` Internal value used in your application logic (max 100 characters) Display text shown to reviewers (max 200 characters) ### Optional Parameters Whether a selection is mandatory for completion ## Implementation Examples ### Basic Approval Workflow Simple approve/reject decision with visual indicators: ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user comment for community guideline compliance:\n\n'This product is amazing! I've been using it for 3 months and it completely changed my workflow. Highly recommend to anyone looking to improve productivity!'", "response_type": "single_select", "response_config": { "options": [ { "value": "approve", "label": "✅ Approve Content" }, { "value": "reject", "label": "❌ Reject Content" }, { "value": "escalate", "label": "🚨 Escalate for Review" } ], "required": True }, "default_response": "reject", "timeout_seconds": 1800, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "high", request_text: "Please review this user comment for community guideline compliance:\n\n'This product is amazing! I've been using it for 3 months and it completely changed my workflow. Highly recommend to anyone looking to improve productivity!'", response_type: "single_select", response_config: { options: [ { value: "approve", label: "✅ Approve Content", description: "Content follows all community guidelines", color: "#22c55e" }, { value: "reject", label: "❌ Reject Content", description: "Content violates community policies", color: "#ef4444" }, { value: "escalate", label: "🚨 Escalate for Review", description: "Unclear case requiring senior reviewer", color: "#8b5cf6" } ], required: true }, default_response: "reject", timeout_seconds: 1800, platform: "api" }; ``` ### Content Classification Categorizing content into specific types: ```python Python theme={null} # Content categorization for routing request_data = { "processing_type": "deferred", "type": "markdown", "priority": "medium", "request_text": "Please categorize this customer inquiry:\n\n'Hi, I'm having trouble with my recent order #12345. The item was supposed to arrive yesterday but I haven't received it yet. Can you help track it down?'", "response_type": "single_select", "response_config": { "options": [ { "value": "shipping_inquiry", "label": "📦 Shipping & Delivery" }, { "value": "product_support", "label": "🛠️ Product Support" }, { "value": "billing_payment", "label": "💳 Billing & Payment" }, { "value": "returns_exchanges", "label": "🔄 Returns & Exchanges" }, { "value": "general_inquiry", "label": "📞 General Inquiry" } ], "required": True }, "default_response": "general_inquiry", "timeout_seconds": 7200, "platform": "api" } ``` ### Quality Assessment Rating content quality with discrete levels: ```python Python theme={null} # Content quality evaluation request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please evaluate the quality of this AI-generated article:\n\n[Article content would be included here...]\n\nFocus on accuracy, readability, and usefulness for our target audience.", "response_type": "single_select", "response_config": { "options": [ { "value": "excellent", "label": "⭐ Excellent Quality" }, { "value": "good", "label": "✨ Good Quality" }, { "value": "fair", "label": "📝 Fair Quality" }, { "value": "poor", "label": "❌ Poor Quality" }, { "value": "unusable", "label": "🚫 Unusable" } ], "required": True }, "default_response": "fair", "timeout_seconds": 3600, "platform": "api" } ``` ## Response Format When a reviewer selects an option, you'll receive the selected value: ```json theme={null} { "response_data": "approve" } ``` ## Use Case Examples ### 1. Content Moderation ```python theme={null} moderation_config = { "response_type": "single_select", "response_config": { "options": [ { "value": "approve", "label": "✅ Approve" }, { "value": "approve_with_warning", "label": "⚠️ Approve with Warning" }, { "value": "reject_minor", "label": "❌ Reject - Minor Violation" }, { "value": "reject_major", "label": "🚨 Reject - Major Violation" }, { "value": "reject_ban", "label": "🛑 Reject - Ban User" } ], "required": True } } ``` ```python theme={null} def handle_moderation_decision(response_data): decision = response_data["selected_value"] label = response_data["selected_label"] if decision == "approve": publish_content() log_decision("approved", label) elif decision == "approve_with_warning": publish_content() send_warning_to_user("Please review community guidelines") log_decision("approved_with_warning", label) elif decision == "reject_minor": remove_content() notify_user("Content removed for minor policy violation") log_moderation_action("content_removed", "minor") elif decision == "reject_major": remove_content() send_formal_warning_to_user() log_moderation_action("content_removed", "major") elif decision == "reject_ban": remove_content() ban_user_account(duration="7_days") send_ban_notification() log_moderation_action("user_banned", "severe") ``` ### 2. Support Ticket Routing ```python theme={null} ticket_routing_config = { "response_type": "single_select", "response_config": { "options": [ { "value": "technical_support", "label": "🔧 Technical Support" }, { "value": "billing_support", "label": "💰 Billing Support" }, { "value": "customer_success", "label": "🤝 Customer Success" }, { "value": "sales_inquiry", "label": "💼 Sales Inquiry" }, { "value": "escalation", "label": "🚨 Executive Escalation" } ], "required": True } } ``` ```python theme={null} def route_support_ticket(ticket_id, response_data): department = response_data["selected_value"] routing_label = response_data["selected_label"] # Route to appropriate team routing_map = { "technical_support": "engineering@company.com", "billing_support": "billing@company.com", "customer_success": "success@company.com", "sales_inquiry": "sales@company.com", "escalation": "executives@company.com" } # Set priority based on routing priority_map = { "technical_support": "medium", "billing_support": "high", "customer_success": "medium", "sales_inquiry": "low", "escalation": "critical" } # Perform routing assign_ticket( ticket_id=ticket_id, department=department, assignee_email=routing_map[department], priority=priority_map[department] ) # Update ticket status update_ticket_status(ticket_id, "routed", routing_label) # Send notifications notify_department(department, ticket_id) ``` ### 3. Document Approval Workflow ```python theme={null} document_approval_config = { "response_type": "single_select", "response_config": { "options": [ { "value": "approved", "label": "✅ Approved for Publication" }, { "value": "approved_with_changes", "label": "📝 Approved with Minor Changes" }, { "value": "needs_revision", "label": "🔄 Needs Revision" }, { "value": "needs_legal_review", "label": "⚖️ Needs Legal Review" }, { "value": "rejected", "label": "❌ Rejected" } ], "required": True } } ``` ```python theme={null} def handle_document_approval(document_id, response_data): status = response_data["selected_value"] decision_label = response_data["selected_label"] if status == "approved": publish_document(document_id) update_document_status(document_id, "published") notify_author("Document approved and published") elif status == "approved_with_changes": update_document_status(document_id, "conditional_approval") create_revision_tasks(document_id) notify_author("Document approved pending minor changes") elif status == "needs_revision": update_document_status(document_id, "revision_required") request_revision_from_author(document_id) set_revision_deadline(document_id, days=7) elif status == "needs_legal_review": update_document_status(document_id, "legal_review") route_to_legal_team(document_id) notify_author("Document sent to legal for review") elif status == "rejected": update_document_status(document_id, "rejected") archive_document(document_id) notify_author("Document rejected - see feedback for details") # Log the decision log_approval_decision(document_id, status, decision_label) ``` ## Validation and Error Handling ### Automatic Validation The mobile app automatically validates single select responses: * **Option validation**: Ensures selected value exists in the options array * **Required validation**: Prevents submission when required=true and no selection made * **Single selection**: Enforces exactly one choice (radio button behavior) ### Processing Validation Your application should validate received responses: ```python theme={null} def validate_single_select_response(response_data, response_config): """Validate single select response against configuration""" # Check response structure if not isinstance(response_data, dict): return False, "Response must be an object" if "selected_value" not in response_data: return False, "Missing selected_value field" # Validate selected value exists in options valid_values = [opt["value"] for opt in response_config["options"]] selected = response_data["selected_value"] if selected not in valid_values: return False, f"Invalid selection: {selected}" # Check required if response_config.get("required", False) and not selected: return False, "Selection is required" return True, "Valid" # Usage is_valid, error_message = validate_single_select_response( response_data={ "selected_value": "approve", "selected_label": "✅ Approve Content" }, response_config={ "options": [...], "required": True } ) ``` ## Best Practices ### Option Design * Use descriptive labels that clearly communicate the choice * Avoid ambiguous or similar-sounding options * Include emoji or icons for visual differentiation * Keep labels concise but informative (under 50 characters ideal) * Put most common/expected choices first * Order by severity (mild to severe) or progression (low to high) * Group related options together * Consider alphabetical ordering for long lists * Provide context for options that might be unclear * Explain consequences or next steps for each choice * Include examples when helpful * Keep descriptions brief but informative * Use green (#22c55e) for positive/approval actions * Use red (#ef4444) for negative/rejection actions * Use yellow/orange (#f59e0b) for warnings or caution * Use blue (#3b82f6) for neutral/informational options * Use purple (#8b5cf6) for escalation or special handling ### Processing Best Practices ```python theme={null} # Clean, maintainable processing logic def process_single_select_decision(response_data, context): decision = response_data["selected_value"] handlers = { "approve": handle_approval, "reject": handle_rejection, "escalate": handle_escalation, "needs_revision": handle_revision_request } handler = handlers.get(decision) if handler: return handler(context) else: log_error(f"Unknown decision: {decision}") return handle_default_case(context) ``` ```python theme={null} # Track decisions for compliance and analysis def log_decision(request_id, response_data, reviewer_info): decision_log = { "request_id": request_id, "decision": response_data["selected_value"], "decision_label": response_data["selected_label"], "reviewer_id": reviewer_info["user_id"], "reviewer_name": reviewer_info["name"], "timestamp": datetime.utcnow().isoformat(), "response_time_seconds": reviewer_info.get("response_time") } store_decision_log(decision_log) update_analytics(decision_log) ``` ```python theme={null} # Analyze decision patterns def analyze_decision_patterns(loop_id, time_period="30d"): decisions = get_decisions_for_period(loop_id, time_period) # Decision distribution decision_counts = Counter(d["decision"] for d in decisions) # Reviewer consistency reviewer_patterns = analyze_reviewer_consistency(decisions) # Response time analysis avg_response_time = calculate_average_response_time(decisions) return { "decision_distribution": dict(decision_counts), "reviewer_consistency": reviewer_patterns, "average_response_time": avg_response_time, "total_decisions": len(decisions) } ``` ## Common Patterns ### Progressive Approval Workflow ```python theme={null} # Multi-stage approval with escalation approval_stages = { "initial_review": { "options": ["approve", "needs_changes", "escalate_to_senior"] }, "senior_review": { "options": ["final_approval", "send_back_for_revision", "escalate_to_executive"] }, "executive_review": { "options": ["executive_approval", "reject_with_explanation"] } } ``` ### Severity-Based Processing ```python theme={null} # Handle different severity levels appropriately severity_handling = { "low": {"priority": "normal", "sla_hours": 24}, "medium": {"priority": "high", "sla_hours": 8}, "high": {"priority": "urgent", "sla_hours": 2}, "critical": {"priority": "immediate", "sla_hours": 1} } ``` ## Next Steps Learn about selecting multiple options from predefined lists Combine structured decisions with detailed feedback Advanced patterns for handling different response types See how single select responses work in the mobile app # Text Responses Source: https://docs.hitl.sh/responses/text Detailed guide to implementing free-form text responses for collecting detailed feedback and explanations from reviewers # Text Responses Text responses allow reviewers to provide free-form written feedback, making them perfect for scenarios requiring detailed explanations, qualitative assessments, or open-ended input that can't be captured through structured options. ## When to Use Text Responses Text responses are ideal for: Detailed feedback on articles, blog posts, or creative content where specific suggestions and explanations are valuable. Explanations of why something passed or failed review, with specific recommendations for improvement. Describing problems, bugs, or policy violations where context and detail are crucial for understanding. Professional opinions, technical assessments, or domain expert evaluations requiring nuanced explanations. ## Configuration Options Text responses support several configuration parameters to control input validation and user experience: ### Required Parameters Maximum number of characters allowed (1-5000) ### Optional Parameters Placeholder text shown in the input field to guide reviewers Minimum number of characters required for the response Whether the response is mandatory or can be left empty ## Implementation Examples ### Basic Text Response Simple text feedback with basic validation: ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this blog post and provide feedback on accuracy, tone, and readability.", "response_type": "text", "response_config": { "max_length": 1000, "placeholder": "Provide your detailed feedback here...", "required": True }, "default_response": "No feedback provided within the review period", "timeout_seconds": 3600, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "medium", request_text: "Please review this blog post and provide feedback on accuracy, tone, and readability.", response_type: "text", response_config: { max_length: 1000, placeholder: "Provide your detailed feedback here...", required: true }, default_response: "No feedback provided within the review period", timeout_seconds: 3600, platform: "api" }; ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/{loop_id}/requests \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this blog post and provide feedback on accuracy, tone, and readability.", "response_type": "text", "response_config": { "max_length": 1000, "placeholder": "Provide your detailed feedback here...", "required": true }, "default_response": "No feedback provided within the review period", "timeout_seconds": 3600, "platform": "api" }' ``` ### Advanced Text Response Text response with length requirements and structured guidance: ```python Python theme={null} # Content editing request with detailed requirements request_data = { "processing_type": "deferred", "type": "markdown", "priority": "low", "request_text": """ Please edit this article draft and provide improvement suggestions: # Article Title: "10 Ways to Improve Your Productivity" Content here would be the actual article... Please focus on: 1. Grammar and spelling corrections 2. Flow and readability improvements 3. Factual accuracy 4. Engagement and tone """, "response_type": "text", "response_config": { "placeholder": "Provide specific editing suggestions with line references where possible. Format: '1. Grammar: Fix X in paragraph 2. 2. Flow: Restructure Y section...'", "min_length": 100, "max_length": 2000, "required": True }, "default_response": "Editorial review not completed within deadline. Recommend postponing publication pending review.", "timeout_seconds": 86400, # 24 hours "platform": "api" } ``` ```javascript Node.js theme={null} // Code review feedback request const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "high", request_text: `Please review this code change and provide feedback: \`\`\`javascript // Code diff or snippet here function processUserData(userData) { // Implementation details... } \`\`\` Focus on security, performance, and maintainability.`, response_type: "text", response_config: { placeholder: "Provide specific feedback on security vulnerabilities, performance issues, code quality, and suggested improvements...", min_length: 50, max_length: 1500, required: true }, default_response: "Code review not completed within deadline. Do not merge pending review.", timeout_seconds: 7200, // 2 hours platform: "api" }; ``` ## Response Format When a reviewer submits a text response, you'll receive: ```json theme={null} { "response_data": "The article is well-structured and informative. Grammar is mostly correct with a few minor issues: 'it's' should be 'its' in paragraph 3, and there's a comma splice in the conclusion. The tone is engaging and appropriate for the target audience. I'd recommend adding one more concrete example in section 4 to strengthen the argument. Overall, this is ready for publication with those minor corrections." } ``` ## Use Case Examples ### 1. Content Editorial Review ```python theme={null} editorial_config = { "response_type": "text", "response_config": { "placeholder": "Provide editorial feedback covering grammar, style, accuracy, and engagement. Include specific suggestions for improvement.", "min_length": 50, "max_length": 1200, "required": True }, "default_response": "Editorial review incomplete - recommend additional review before publication" } ``` ```json theme={null} { "response_data": "Article structure is solid with clear progression. Grammar issues: 'affect' vs 'effect' in para 2, missing serial comma in bullet list. Style: tone shifts between formal/casual - recommend consistency. Content accuracy verified against cited sources. Suggest stronger opening hook and more specific examples in conclusion. Overall grade: B+ with recommended revisions." } ``` ```python theme={null} def process_editorial_feedback(response_data): feedback = response_data # Parse feedback for common patterns if "grammar issues:" in feedback.lower(): flag_for_copy_editing() if "accuracy verified" in feedback.lower(): mark_factually_approved() if "grade: a" in feedback.lower(): approve_for_publication() elif "grade: b" in feedback.lower(): require_minor_revisions() else: require_major_revisions() # Store full feedback for author save_feedback_for_author(feedback) ``` ### 2. Bug Report Verification ```python theme={null} bug_verification_config = { "response_type": "text", "response_config": { "placeholder": "Describe steps taken to reproduce the bug, environment details, and whether you confirmed the issue. Include severity assessment.", "min_length": 75, "max_length": 800, "required": True }, "default_response": "Bug verification not completed within SLA timeframe" } ``` ```json theme={null} { "response_data": "Reproduced on Chrome 120.0, Firefox 119.0, Safari 17.1 using provided steps. Issue occurs consistently when user has >100 items in cart. Error appears in console: 'Cannot read property length of undefined' in checkout.js:247. Workaround: clear cart and re-add items. Severity: Medium - affects checkout but has workaround. Recommend priority fix for next sprint." } ``` ```python theme={null} def process_bug_verification(response_data): feedback = response_data # Extract severity if "severity: critical" in feedback.lower(): set_bug_priority("P0") elif "severity: high" in feedback.lower(): set_bug_priority("P1") elif "severity: medium" in feedback.lower(): set_bug_priority("P2") else: set_bug_priority("P3") # Check if reproduced if "reproduced" in feedback.lower(): confirm_bug_exists() elif "cannot reproduce" in feedback.lower(): mark_as_works_as_designed() # Store detailed feedback update_bug_report(feedback) ``` ### 3. Expert Consultation ```python theme={null} expert_consultation_config = { "response_type": "text", "response_config": { "placeholder": "Provide your expert analysis including key insights, recommendations, risk assessment, and suggested next steps. Include confidence level in your assessment.", "min_length": 200, "max_length": 3000, "required": True }, "default_response": "Expert consultation not completed within consultation period" } ``` ```json theme={null} { "response_data": "Technical Analysis: The proposed architecture shows good scalability patterns but has potential security vulnerabilities in the API gateway layer. Recommendations: 1) Implement OAuth 2.0 with PKCE, 2) Add rate limiting per endpoint, 3) Consider edge caching for static content. Risk Assessment: Medium risk if deployed as-is, low risk with recommended changes. Cost implications: Additional $2-3k monthly for security infrastructure. Timeline: 2-3 weeks for full implementation. Confidence: High (8/10) based on similar implementations. Next steps: Security audit, load testing, phased rollout." } ``` ```python theme={null} def process_expert_consultation(response_data): feedback = response_data # Extract confidence level import re confidence_match = re.search(r'confidence[:\s]+(?:high|medium|low|\d+/10)', feedback.lower()) if confidence_match: confidence = confidence_match.group() store_confidence_rating(confidence) # Extract recommendations if "recommendations:" in feedback.lower(): recommendations_section = extract_section_after("recommendations:", feedback) parse_recommendations(recommendations_section) # Flag for executive summary if high-stakes if "risk assessment: high" in feedback.lower(): escalate_to_executives() # Store full consultation save_expert_consultation(feedback) ``` ## Validation and Error Handling ### Client-Side Validation The mobile app automatically validates text responses: * **Length checking**: Prevents submission if outside min/max bounds * **Required validation**: Blocks empty submissions when required=true * **Character counting**: Shows real-time character count to reviewers * **Whitespace handling**: Trims leading/trailing whitespace before validation ### Server-Side Validation Your application should also validate responses: ```python theme={null} def validate_text_response(response_data, response_config): """Validate text response against configuration""" if not isinstance(response_data, str): return False, "Response must be text" # Check required if response_config.get("required", False) and not response_data.strip(): return False, "Response is required" # Check length bounds text_length = len(response_data.strip()) min_length = response_config.get("min_length", 0) max_length = response_config.get("max_length", 5000) if text_length < min_length: return False, f"Response too short (minimum {min_length} characters)" if text_length > max_length: return False, f"Response too long (maximum {max_length} characters)" return True, "Valid" # Usage is_valid, error_message = validate_text_response( response_data="This is the reviewer's feedback...", response_config={ "min_length": 20, "max_length": 500, "required": True } ) ``` ## Best Practices ### Configuration Best Practices * **Short feedback**: 50-300 characters for quick comments * **Detailed reviews**: 200-1500 characters for thorough analysis * **Expert consultations**: 500-3000 characters for comprehensive assessments * Avoid extremes: too short limits valuable feedback, too long reduces completion rates * Use descriptive placeholders that explain what you're looking for * Include examples of good feedback in the request\_text * Specify focus areas or structured format when helpful * Guide reviewers on the level of detail expected * Time-sensitive requests: shorter responses, clear urgency indicators * Expert reviews: longer limits, domain-specific guidance * Quality checks: structured feedback requests with specific criteria * Editorial reviews: formatting suggestions and style guide references ### Processing Best Practices ```python theme={null} # Extract actionable insights from text feedback def analyze_text_feedback(feedback_text): insights = { "sentiment": analyze_sentiment(feedback_text), "action_items": extract_action_items(feedback_text), "mentioned_issues": identify_issues(feedback_text), "confidence_indicators": find_confidence_signals(feedback_text) } return insights ``` ```python theme={null} # Combine multiple text responses for consensus def aggregate_text_responses(responses): common_themes = identify_common_themes(responses) conflicting_opinions = find_disagreements(responses) return { "consensus_points": common_themes, "areas_of_disagreement": conflicting_opinions, "recommendation": generate_final_recommendation(responses) } ``` ```python theme={null} # Score text response quality and usefulness def score_response_quality(response_text, criteria): scores = {} # Length appropriateness scores["length"] = evaluate_response_length(response_text) # Specificity and actionability scores["specificity"] = count_specific_examples(response_text) # Coverage of requested criteria scores["coverage"] = check_criteria_coverage(response_text, criteria) return calculate_overall_score(scores) ``` ## Next Steps Learn about structured decision-making with predefined options Combine text feedback with categorical selections Understand how to handle and analyze different response types See how reviewers interact with text responses on mobile # Response Types Source: https://docs.hitl.sh/responses/types Complete guide to all response types supported by HITL.sh - from simple text to complex ratings and multi-select options # Response Types HITL.sh supports six different response types that allow reviewers to provide structured feedback. Each response type has its own configuration options and validation rules, giving you flexibility to design the perfect review experience for your use case. ## Overview When creating a request, you specify the `response_type` and `response_config` to define how reviewers will respond. The response type determines the UI reviewers see in the mobile app and how their responses are structured and validated. Free-form text responses with character limits and validation Send a draft for the reviewer to revise before sending back Choose one option from a predefined list with labels Choose multiple options with minimum/maximum selection limits Numeric ratings with custom scales, steps, and labeled endpoints Numeric input with ranges, decimal places, and formatting ## Text Response Free-form text input allowing reviewers to provide detailed written feedback. ### Configuration Placeholder text shown in the input field Minimum number of characters required Maximum number of characters allowed (1-5000) Whether the response is required ### Example Request ```python Python theme={null} import requests request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this article and provide detailed feedback on accuracy and tone.", "response_type": "text", "response_config": { "placeholder": "Provide your detailed feedback here...", "min_length": 50, "max_length": 1000, "required": True }, "default_response": "No feedback provided within timeout period", "timeout_seconds": 3600, "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "medium", request_text: "Please review this article and provide detailed feedback on accuracy and tone.", response_type: "text", response_config: { placeholder: "Provide your detailed feedback here...", min_length: 50, max_length: 1000, required: true }, default_response: "No feedback provided within timeout period", timeout_seconds: 3600, platform: "api" }; const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${loopId}/requests`, requestData, { headers: { Authorization: `Bearer ${apiKey}` }} ); ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/{loop_id}/requests \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review this article and provide detailed feedback on accuracy and tone.", "response_type": "text", "response_config": { "placeholder": "Provide your detailed feedback here...", "min_length": 50, "max_length": 1000, "required": true }, "default_response": "No feedback provided within timeout period", "timeout_seconds": 3600, "platform": "api" }' ``` ### Response Format When a reviewer submits a text response, you'll receive: ```json theme={null} { "response_data": "This article is well-written and factually accurate. The tone is professional and engaging. I recommend approval with minor grammar corrections on paragraph 3." } ``` ## Editable Text Response Send a draft message for the reviewer to edit and revise before sending back. Perfect for AI-generated notifications, emails, or messages that need human polish. ### Configuration The initial draft text that the reviewer will edit Placeholder text shown in the editor Minimum characters required Maximum characters allowed (1-10000) Whether a response is required ### Example Request ```python Python theme={null} import requests request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review and edit this AI-generated client notification before it is sent.", "response_type": "editable_text", "response_config": { "draft_text": "Dear Client,\n\nYour project milestone has been completed ahead of schedule. Our team will reach out shortly to discuss next steps.\n\nBest regards,\nThe Team", "placeholder": "Edit the message above...", "min_length": 20, "max_length": 2000, "required": True }, "default_response": "Notification not reviewed within timeout period", "timeout_seconds": 3600, "platform": "api" } response = requests.post( f"https://api.hitl.sh/v1/api/loops/{loop_id}/requests", headers={"Authorization": f"Bearer {api_key}"}, json=request_data ) ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "medium", request_text: "Please review and edit this AI-generated client notification before it is sent.", response_type: "editable_text", response_config: { draft_text: "Dear Client,\n\nYour project milestone has been completed ahead of schedule. Our team will reach out shortly to discuss next steps.\n\nBest regards,\nThe Team", placeholder: "Edit the message above...", min_length: 20, max_length: 2000, required: true }, default_response: "Notification not reviewed within timeout period", timeout_seconds: 3600, platform: "api" }; const response = await axios.post( `https://api.hitl.sh/v1/api/loops/${loopId}/requests`, requestData, { headers: { Authorization: `Bearer ${apiKey}` }} ); ``` ```bash cURL theme={null} curl -X POST https://api.hitl.sh/v1/api/loops/{loop_id}/requests \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please review and edit this AI-generated client notification before it is sent.", "response_type": "editable_text", "response_config": { "draft_text": "Dear Client,\n\nYour project milestone has been completed ahead of schedule. Our team will reach out shortly to discuss next steps.\n\nBest regards,\nThe Team", "placeholder": "Edit the message above...", "min_length": 20, "max_length": 2000, "required": true }, "default_response": "Notification not reviewed within timeout period", "timeout_seconds": 3600, "platform": "api" }' ``` ### Response Format When a reviewer submits an editable text response, you'll receive: ```json theme={null} { "response_data": { "revised_text": "Dear Client,\n\nGreat news — your project milestone has been completed two days ahead of schedule! We will be in touch by end of day to walk you through the next phase.\n\nWarm regards,\nThe Team", "was_edited": true } } ``` ## Single Select Response Allow reviewers to choose one option from a predefined list. ### Configuration Array of options (1-20 options max). Can be simple strings or SelectOption objects. ```json theme={null} ["Option 1", "Option 2", "Option 3"] ``` Automatically converted to rich SelectOption objects for mobile app compatibility. Internal value for the option (max 100 chars) Display text for the option (max 200 chars) Whether a selection is required ### Example Request ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "high", "request_text": "Please review this user comment for community guideline compliance:", "response_type": "single_select", "response_config": { "options": [ "Approve", "Approve with Warning", "Reject", "Escalate" ] }, "default_response": "reject", "timeout_seconds": 1800, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "high", request_text: "Please review this user comment for community guideline compliance:", response_type: "single_select", response_config: { options: [ "Approve", "Approve with Warning", "Reject", "Escalate" ] }, default_response: "reject", timeout_seconds: 1800, platform: "api" }; ``` ### Response Format ```json theme={null} { "response_data": "approve_with_warning" } ``` ## Multi Select Response Allow reviewers to choose multiple options from a predefined list. ### Configuration Array of options (1-20 options max). Can be simple strings or SelectOption objects. ```json theme={null} ["Option 1", "Option 2", "Option 3"] ``` Automatically converted to rich SelectOption objects for mobile app compatibility. Internal value for the option (max 100 chars) Display text for the option (max 200 chars) Maximum number of options that can be selected Minimum number of options that must be selected (auto-added when max\_selections is provided) Whether at least one selection is required ### Example Request ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "What issues do you see in this business listing? Select all that apply:", "response_type": "multi_select", "response_config": { "options": [ "Incorrect Address", "Wrong Phone Number", "Outdated Hours", "Poor Quality Photos", "Duplicate Listing", "No Issues Found" ], "max_selections": 5 }, "default_response": [], "timeout_seconds": 2400, "platform": "api" } ``` ### Response Format ```json theme={null} { "response_data": ["incorrect_address", "outdated_hours"] } ``` ## Rating Response Numeric rating scale with configurable range and step values. ### Configuration Maximum value of the rating scale Minimum value of the rating scale (must be \< scale\_max) Step increment for the rating scale (e.g., 0.5 for half-star ratings, 1 for full-star ratings) Whether a rating is required ### Example Request ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "Please rate the quality of this AI-generated content on a scale of 1-10:", "response_type": "rating", "response_config": { "scale_min": 1, "scale_max": 10, "scale_step": 0.5, "required": true }, "default_response": 5, "timeout_seconds": 1800, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "medium", request_text: "Please rate the quality of this AI-generated content on a scale of 1-10:", response_type: "rating", response_config: { scale_min: 1, scale_max: 10, scale_step: 0.5, required: true }, default_response: 5, timeout_seconds: 1800, platform: "api" }; ``` ### Response Format ```json theme={null} { "response_data": 7.5 } ``` ## Number Response Numeric input with validation and formatting options. ### Configuration Maximum allowed value Minimum allowed value (must be \< max\_value) Number of decimal places allowed (0-10) Whether negative numbers are allowed Whether a value is required ### Example Request ```python Python theme={null} request_data = { "processing_type": "time-sensitive", "type": "markdown", "priority": "medium", "request_text": "What's a fair market price for this product based on your expertise?", "response_type": "number", "response_config": { "max_value": 10000, # min_value defaults to 1 # decimal_places defaults to 2 # allow_negative defaults to false # required defaults to false }, "default_response": 50, "timeout_seconds": 2400, "platform": "api" } ``` ```javascript Node.js theme={null} const requestData = { processing_type: "time-sensitive", type: "markdown", priority: "medium", request_text: "What's a fair market price for this product based on your expertise?", response_type: "number", response_config: { max_value: 10000, // min_value defaults to 1 // decimal_places defaults to 2 // allow_negative defaults to false // required defaults to false }, default_response: 50, timeout_seconds: 2400, platform: "api" }; ``` ### Response Format ```json theme={null} { "response_data": 299.99 } ``` ## Validation Rules HITL.sh validates all responses against the configured rules: * Response must be a string * Length must be within min\_length and max\_length bounds * Required responses cannot be empty strings * Response must be a valid option value from the options array * Required responses must include a selection * Only one option can be selected * All selected values must be valid options from the options array * Number of selections must be within min\_selections and max\_selections bounds * No duplicate selections allowed * Response must be a number within scale\_min and scale\_max bounds * Value must align with scale\_step increments (e.g., only .0 and .5 for step=0.5) * Required ratings cannot be null * Response must be a number within min\_value and max\_value bounds * Decimal places must not exceed configured decimal\_places * Negative numbers only allowed if allow\_negative is true * Response must be an object containing `revised_text` (string) and `was_edited` (boolean) * `revised_text` length must be within min\_length and max\_length bounds * Required responses cannot have an empty `revised_text` * `max_length` must be between 1 and 10000 ## Best Practices ### Choosing Response Types Use text responses when you need detailed explanations, qualitative feedback, or open-ended input that can't be captured in predefined options. Use single select for clear decisions with mutually exclusive options. Perfect for approval workflows, categorization, and status assignments. Use multi select when multiple aspects need to be evaluated simultaneously, such as content issues, feature requests, or compliance checklist items. Use ratings for quantitative assessments where you need to measure quality, satisfaction, confidence levels, or performance on a scale. Use number responses for pricing, quantities, measurements, or any numeric data that needs validation and formatting. Use editable text when you have AI-generated drafts — emails, notifications, or messages — that need a human to refine the tone, accuracy, or wording before delivery. ### Configuration Tips Use descriptive labels and include helpful descriptions for select options. Consider adding colors for visual clarity. Configure appropriate min/max values, character limits, and selection bounds to prevent invalid or unusable responses. Always specify meaningful default responses that represent the safest or most common expected outcome. Remember that reviewers will interact with these response types on mobile devices. Keep options concise and touch-friendly. ### Response Handling When processing responses in your application: ```python theme={null} def handle_response(request_id, response_data, response_type): """Handle different response types appropriately""" if response_type == "text": text = response_data # Process free-form text feedback analyze_sentiment(text) extract_keywords(text) elif response_type == "single_select": selected = response_data if selected == "approve": approve_content() elif selected == "reject": reject_content() elif selected == "escalate": escalate_to_senior_reviewer() elif response_type == "multi_select": issues = response_data for issue in issues: handle_identified_issue(issue) elif response_type == "rating": score = response_data if score >= 8: mark_as_high_quality() elif score <= 3: flag_for_improvement() elif response_type == "number": value = response_data update_pricing_model(request_id, value) elif response_type == "editable_text": revised_text = response_data["revised_text"] was_edited = response_data["was_edited"] send_client_notification(revised_text) if was_edited: log_human_revision(request_id, revised_text) ``` ## Next Steps Start using these response types in your requests See how reviewers interact with these response types Set up webhooks to receive responses in real-time Learn how to integrate HITL.sh with practical examples