Skip to main content

Overview

This endpoint processes user decisions and forwards them to the originating AI service. The required payload structure varies based on the action’s response_type as defined in the notification.

Request

Endpoint: POST /api/v1/client/respond
Authentication: Requires Bearer token using user credentials

Request Body

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "approve",
  "response_data": null
}
FieldTypeRequiredDescription
notification_idstringYesUUID of the notification being answered
action_idstringYesSelected action identifier from the notification
response_dataanyConditionalRequired for all response types except "simple"
The response_data field must conform to the requirements of the action’s response_type. For details on the format for each response type, see the Response Types documentation.

Response

Success Response

Status Code: 200 OK
{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "approve",
  "status": "responded",
  "responded_at": "2025-05-25T10:35:12Z"
}
FieldTypeDescription
notification_idstringUUID of the notification that was answered
action_idstringIdentifier of the selected action
statusstringUpdated notification status, typically “responded”
responded_atdatetimeTimestamp when the response was recorded

Error Responses

Status CodeError CodeDescription
400 Bad RequestMISSING_REQUIRED_FIELDRequired field is missing from request
400 Bad RequestINVALID_ACTION_IDThe specified action_id doesn’t exist for this notification
400 Bad RequestINVALID_RESPONSE_DATAResponse data doesn’t match expected format
401 UnauthorizedAUTH_INVALID_TOKENUser token is invalid
403 ForbiddenNOTIFICATION_ACCESS_DENIEDUser does not have access to this notification
404 Not FoundNOTIFICATION_NOT_FOUNDNotification with specified ID does not exist
409 ConflictNOTIFICATION_EXPIREDNotification deadline has passed
409 ConflictNOTIFICATION_INVALIDATEDService marked notification as invalid
409 ConflictNOTIFICATION_ALREADY_RESPONDEDNotification has already been answered
422 Unprocessable EntityCONSTRAINT_VIOLATIONResponse violates defined constraints

Response Type Examples

Here are examples of valid request bodies for each response type:

Simple Action

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "approve",
  "response_data": null
}

Binary Choice

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "include_logs",
  "response_data": true
}

Single Choice

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "select_action",
  "response_data": "option1"
}

Multiple Choice

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "select_recipients",
  "response_data": ["engineering", "security"]
}

Text Input

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "feedback",
  "response_data": "The suggestion looks good overall, but we should consider the impact on mobile users."
}

Numeric Input

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "set_threshold",
  "response_data": 0.75
}

Scale/Range

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "confidence_rating",
  "response_data": 4
}

Examples

JavaScript Example

async function submitResponse(notificationId, actionId, responseData) {
  try {
    const response = await fetch(
      'https://atp.example.com/api/v1/client/respond',
      {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer user_token_xyz789',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          notification_id: notificationId,
          action_id: actionId,
          response_data: responseData
        })
      }
    );
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`Error ${response.status}: ${errorData.message}`);
    }
    
    const data = await response.json();
    console.log(`Response submitted successfully: ${data.status}`);
    
    return data;
  } catch (error) {
    console.error('Error submitting response:', error);
    throw error;
  }
}

// Usage examples for different response types
submitResponse('550e8400-e29b-41d4-a716-446655440000', 'approve', null);
submitResponse('550e8400-e29b-41d4-a716-446655440000', 'include_logs', true);
submitResponse('550e8400-e29b-41d4-a716-446655440000', 'select_action', 'option1');
submitResponse('550e8400-e29b-41d4-a716-446655440000', 'feedback', 'Looks good!');

Python Example

import requests

def submit_response(token, notification_id, action_id, response_data=None):
    url = 'https://atp.example.com/api/v1/client/respond'
    
    payload = {
        'notification_id': notification_id,
        'action_id': action_id
    }
    
    # Only include response_data if it's not None
    if response_data is not None:
        payload['response_data'] = response_data
    
    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }
    
    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        
        data = response.json()
        print(f"Response submitted successfully: {data['status']}")
        
        return data
    except requests.exceptions.HTTPError as e:
        error_data = e.response.json()
        print(f"Error {e.response.status_code}: {error_data['message']}")
        raise

# Usage examples for different response types
submit_response('user_token_xyz789', '550e8400-e29b-41d4-a716-446655440000', 'approve')
submit_response('user_token_xyz789', '550e8400-e29b-41d4-a716-446655440000', 'include_logs', True)
submit_response('user_token_xyz789', '550e8400-e29b-41d4-a716-446655440000', 'select_action', 'option1')
submit_response('user_token_xyz789', '550e8400-e29b-41d4-a716-446655440000', 'feedback', 'Looks good!')

Error Handling

When submitting responses, it’s important to handle errors gracefully. Here’s a more comprehensive error handling approach:
async function submitResponseWithRetry(notificationId, actionId, responseData) {
  const MAX_RETRIES = 3;
  let attempt = 0;
  
  while (attempt <= MAX_RETRIES) {
    try {
      const response = await fetch(
        'https://atp.example.com/api/v1/client/respond',
        {
          method: 'POST',
          headers: {
            'Authorization': 'Bearer user_token_xyz789',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            notification_id: notificationId,
            action_id: actionId,
            response_data: responseData
          })
        }
      );
      
      if (response.ok) {
        return await response.json();
      }
      
      const errorData = await response.json();
      
      // Handle specific error cases
      switch (errorData.code) {
        case 'NOTIFICATION_EXPIRED':
        case 'NOTIFICATION_INVALIDATED':
        case 'NOTIFICATION_ALREADY_RESPONDED':
          // Terminal state, don't retry
          throw new Error(`${errorData.code}: ${errorData.message}`);
          
        case 'INVALID_RESPONSE_DATA':
        case 'CONSTRAINT_VIOLATION':
          // Client error, don't retry
          throw new Error(`${errorData.code}: ${errorData.message}`);
          
        case 'RATE_LIMIT_EXCEEDED':
          // Rate limiting, retry with backoff
          const retryAfter = response.headers.get('Retry-After');
          const delayMs = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
          await new Promise(resolve => setTimeout(resolve, delayMs));
          attempt++;
          continue;
      }
      
      // Server errors (5xx) are retryable
      if (response.status >= 500) {
        const delayMs = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delayMs));
        attempt++;
        continue;
      }
      
      // Other errors are considered permanent
      throw new Error(`${errorData.code}: ${errorData.message}`);
      
    } catch (error) {
      // Network errors are retryable
      if (error.name === 'TypeError' && attempt < MAX_RETRIES) {
        const delayMs = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delayMs));
        attempt++;
        continue;
      }
      throw error;
    }
  }
  
  // If we've exhausted retries
  throw new Error('Maximum retry attempts reached');
}

Best Practices

  1. Validate Response Data: Verify that response data matches the expected format before submitting
  2. Handle Terminal States: Gracefully handle expired or invalidated notifications
  3. Implement Retries: Use exponential backoff for retrying transient failures
  4. Offline Support: Queue responses locally when offline and submit when connectivity is restored
  5. User Feedback: Provide immediate feedback to users while the response is being processed
  6. Security: Never log or expose sensitive response data
I