> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/appertafoundation/openeyes/llms.txt
> Use this file to discover all available pages before exploring further.

# Events API

> Manage clinical events, signatures, and attachments via the OpenEyes API

## Overview

The Events API provides endpoints for managing clinical events, digital signatures, and event attachments. This includes integration with external devices and systems for importing clinical data.

## API Modules

The Events API is split across multiple modules:

* **Sign Module**: Digital signature capture and validation
* **Request Module**: External data integration and queue management
* **Attachment Module**: Event attachment display and management

## Event Model Reference

The Event model (`protected/models/Event.php`) represents clinical events:

### Core Attributes

* `id` - Event unique identifier
* `episode_id` - Associated episode ID
* `user_id` - User who created the event
* `event_type_id` - Type of clinical event
* `info` - Additional event information
* `deleted` - Soft delete flag
* `delete_reason` - Reason for deletion
* `is_automated` - Whether event was auto-generated
* `automated_source` - JSON structure describing automation source
* `event_date` - Date of the clinical event
* `created_date` - Record creation timestamp
* `last_modified_date` - Last modification timestamp
* `firm_id` - Associated firm/service
* `parent_id` - Parent event (for linked events)

### Related Models

* `episode` - Patient episode
* `eventType` - Event type definition
* `user` - Creating user
* `eventAttachmentGroups[]` - Attached files/images
* `parent` - Parent event
* `children[]` - Child events

Source: `protected/models/Event.php:1-100`

## Digital Signatures API

### Add Signature

Import a digital signature for a clinical event.

<CodeGroup>
  ```http Request theme={null}
  POST /api/v1/sign/add
  Content-Type: application/json
  ```

  ```bash cURL theme={null}
  curl -X POST \
    https://your-instance.com/api/v1/sign/add \
    -H "Content-Type: application/json" \
    -d '{
      "unique_identifier": "event-unique-code",
      "extra_info": "{\"e_id\": 123, \"e_t_id\": 456}",
      "image": "base64_encoded_signature_image"
    }'
  ```

  ```javascript JavaScript theme={null}
  fetch('https://your-instance.com/api/v1/sign/add', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      unique_identifier: 'event-unique-code',
      extra_info: JSON.stringify({
        e_id: 123,
        e_t_id: 456
      }),
      image: base64SignatureData
    })
  })
  .then(response => response.json())
  .then(data => console.log(data));
  ```
</CodeGroup>

#### Request Body

<ParamField body="unique_identifier" type="string" required>
  Unique code identifying the event. Validated using `UniqueCodes::eventFromUniqueCode()`.
</ParamField>

<ParamField body="extra_info" type="string" required>
  JSON string containing element information:

  * `e_id` (integer, required) - Element ID
  * `e_t_id` (integer, required) - Element type ID

  Example: `{"e_id": 123, "e_t_id": 456}`
</ParamField>

<ParamField body="image" type="string" required>
  Base64-encoded signature image (PNG format recommended)
</ParamField>

<ParamField body="origImage" type="string" optional>
  Base64-encoded original signature image before cropping
</ParamField>

<ParamField body="original_log_id" type="integer" optional>
  For manual crop operations, references the original import log ID
</ParamField>

#### Response

**Success Response:**

```json theme={null}
"Correct signature saved!"
```

HTTP Status: `200 OK`

**Error Response:**

```json theme={null}
"Failed signature save!"
```

HTTP Status: `200 OK` (note: error returned with 200 status)

**Bad Request:**

```json theme={null}
"Bad request"
```

HTTP Status: `400 Bad Request`

#### Validation Errors

The endpoint validates:

1. **Missing unique\_identifier**: `"Missing unique_identifier"`
2. **Missing extra\_info**: `"Missing element and element type ids"`
3. **Missing e\_id in extra\_info**: `"Missing element id."`
4. **Missing e\_t\_id in extra\_info**: `"Missing element type id."`
5. **Event not found**: `"Event not found"`
6. **Invalid unique code**: `"Invalid UniqueCode or wrong event"`

Source: `protected/modules/Api/controllers/SignController.php:52-95`

#### Access Control

<Warning>
  The Sign API allows access to all users (`'users' => ['*']`). No authentication is required for the `/sign/add` endpoint. This is intentional for external signature capture devices.
</Warning>

Source: `SignController.php:37-49`

#### CORS Support

The endpoint includes CORS headers:

```php theme={null}
header('Access-Control-Allow-Origin: *');
```

Source: `SignController.php:142`

### Implementation Details

The signature import process (lines 140-209):

1. Accepts POST request with JSON body
2. Validates unique identifier and retrieves event
3. Validates request data structure
4. Saves signature image to protected file storage
5. Creates signature element and associates with event
6. Logs import attempt with status (success/failure)
7. Updates CVI event information if applicable
8. Returns success or error message

### Signature File Storage

Signatures are stored using the `ProtectedFile` model:

```php theme={null}
public function saveToProtectedFile($data)
{
    $filename = "sign_" . md5(time()) . ".png";
    $protected_file = \ProtectedFile::createForWriting($filename);
    file_put_contents($protected_file->getPath(), base64_decode($data['origImage']??$data['image']));
    $protected_file->title = "";
    $protected_file->description = "";
    $protected_file->validate();
    $protected_file->save(false);
    return $protected_file;
}
```

Source: `SignController.php:102-112`

### Signature Import Logging

All signature import attempts are logged:

```php theme={null}
public function saveSignatureImportLog($protected_file, $status_id, $event = null, $return_message = '', $cropped_file_id = null)
{
    $signature_import_log = new SignatureImportLog();
    $signature_import_log->filename = $protected_file->getPath();
    $signature_import_log->cropped_file_id = $cropped_file_id;
    $signature_import_log->status_id = $status_id;
    $signature_import_log->return_message = $return_message;
    $signature_import_log->event_id = $event->id ?? null;
    $signature_import_log->import_datetime = date('Y-m-d H:i:s');
    $signature_import_log->save(false);
    return true;
}
```

Source: `SignController.php:119-130`

## Event Attachments API

### View Attachment

Retrieve an event attachment by ID.

<CodeGroup>
  ```http Request theme={null}
  GET /api/v1/attachmentdisplay/view?id={id}&mime={mime_type}&attachment={field}
  ```

  ```bash cURL theme={null}
  curl -X GET \
    "https://your-instance.com/api/v1/attachmentdisplay/view?id=123&mime=image/png&attachment=blob_data" \
    -u username:password
  ```
</CodeGroup>

#### Query Parameters

<ParamField query="id" type="integer" required>
  Attachment data ID from the `attachment_data` table
</ParamField>

<ParamField query="mime" type="string" optional>
  MIME type for Content-Type header (e.g., `image/png`, `image/jpeg`, `application/pdf`)
</ParamField>

<ParamField query="attachment" type="string" optional>
  Field name in the AttachmentData model containing the binary data
</ParamField>

#### Response

Returns the raw attachment data with appropriate headers:

```http theme={null}
HTTP/1.1 200 OK
Content-Type: image/png
Cache-Control: private, max-age=31536000, immutable
Content-Transfer-Encoding: binary
Last-Modified: Mon, 09 Mar 2026 10:30:00 GMT
Content-Length: 45678

[binary data]
```

#### Caching

The endpoint implements conditional GET with ETags:

* Returns `304 Not Modified` if client cache is current
* Uses `If-Modified-Since` header for validation
* Sets immutable cache for 1 year (attachments are versioned by ID)

Source: `AttachmentDisplayController.php:53-93`

#### Authentication

Requires authenticated user:

```php theme={null}
public function accessRules()
{
    return array(
        array(
            'allow',
            'actions' => array('view', 'create'),
            'users' => array('@'),
        ),
        array(
            'deny',
            'users' => array('*'),
        ),
    );
}
```

Source: `AttachmentDisplayController.php:38-50`

## Request Queue API

### Add to Queue

Submit external device data to the processing queue.

<CodeGroup>
  ```http Request theme={null}
  POST /api/v1/request/queue/add
  Content-Type: application/json
  ```

  ```bash cURL theme={null}
  curl -X POST \
    https://your-instance.com/api/v1/request/queue/add \
    -u username:password \
    -H "Content-Type: application/json" \
    -d '{
      "request_type": "diagnostic_data",
      "system_message": "OCT scan data",
      "patient_id": 12345,
      "data": {...}
    }'
  ```
</CodeGroup>

#### Request Body

<ParamField body="request_type" type="string" optional>
  Type of request being submitted (e.g., "diagnostic\_data", "imaging", "form\_data")
</ParamField>

<ParamField body="system_message" type="string" optional>
  Message from the external system describing the data
</ParamField>

<ParamField body="data" type="object" required>
  The actual data payload. Structure depends on content type and request handler.
</ParamField>

#### Content Types Supported

The endpoint supports multiple content types:

* `application/json`
* `multipart/form-data`
* `application/x-www-form-urlencoded`

Handlers are dynamically loaded based on Content-Type header.

Source: `QueueController.php:26-30`

#### Response

**Success Response:**

```json theme={null}
{
  "request_id": 789
}
```

HTTP Status: `200 OK`

**Validation Error Response:**

```json theme={null}
{
  "success": 0,
  "message": {
    "field_name": "Error description"
  }
}
```

HTTP Status: `422 Unprocessable Entity`

**System Error Response:**

```json theme={null}
{
  "success": 0,
  "message": {
    "system": "Exception message"
  }
}
```

HTTP Status: `500 Internal Server Error`

Source: `QueueController.php:36-48`

### Request Processing Flow

1. Determine content type from header or GET parameter
2. Instantiate appropriate handler class dynamically
3. Handler validates and processes request data
4. Data saved to queue for async processing
5. Return request ID or error details

Source: `QueueController.php:21-51`

## Event Attachment Groups

Event attachments are organized in groups associated with event elements.

### EventAttachmentGroup Model

**Attributes:**

* `id` - Group identifier
* `event_id` - Associated event ID
* `element_type_id` - Element type this group belongs to

**Relations:**

* `event` - Parent event
* `elementType` - Element type definition
* `eventAttachmentItems[]` - Individual attachment items in this group

Source: `protected/modules/Api/modules/Request/models/EventAttachmentGroup.php:17-79`

### Example: Retrieving Event Attachments

```php theme={null}
// Get event
$event = Event::model()->findByPk($event_id);

// Get all attachment groups
foreach ($event->eventAttachmentGroups as $group) {
    echo "Element Type: " . $group->elementType->name . "\n";
    
    // Get items in this group
    foreach ($group->eventAttachmentItems as $item) {
        echo "Attachment: " . $item->attachment_data_id . "\n";
    }
}
```

## URL Routing

### Main API Routes

```php theme={null}
['Api/<controller>/<action>', 'pattern' => 'api/v1/<controller>/<action>', 'verb' => 'GET, POST, PUT']
```

Source: `protected/modules/Api/config/common.php:21`

### Request Module Routes

```php theme={null}
['Api/Request/<controller>/<action>', 'pattern' => 'api/v1/request/<controller:\w+>/<action>', 'verb' => 'GET, POST, PUT']
```

Source: `protected/modules/Api/modules/Request/config/common.php`

## Integration Examples

### Python: Import Signature

```python theme={null}
import requests
import base64
import json

def import_signature(image_path, unique_code, element_id, element_type_id):
    # Read and encode image
    with open(image_path, 'rb') as f:
        image_data = base64.b64encode(f.read()).decode('utf-8')
    
    # Prepare request
    url = 'https://openeyes.example.com/api/v1/sign/add'
    payload = {
        'unique_identifier': unique_code,
        'extra_info': json.dumps({
            'e_id': element_id,
            'e_t_id': element_type_id
        }),
        'image': image_data
    }
    
    # Send request
    response = requests.post(url, json=payload)
    return response.text

result = import_signature(
    'signature.png',
    'ABC123XYZ',
    element_id=456,
    element_type_id=12
)
print(result)
```

### JavaScript: Queue External Data

```javascript theme={null}
async function queueDeviceData(deviceData) {
  const credentials = btoa('username:password');
  
  const response = await fetch('https://openeyes.example.com/api/v1/request/queue/add', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      request_type: 'oct_scan',
      system_message: 'OCT data from Device XYZ',
      data: deviceData
    })
  });
  
  const result = await response.json();
  
  if (result.request_id) {
    console.log(`Queued with ID: ${result.request_id}`);
  } else {
    console.error('Error:', result.message);
  }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Signature Validation">
    Always validate the `unique_identifier` is correct before submitting signatures. Invalid codes cannot be corrected after submission.
  </Accordion>

  <Accordion title="Image Encoding">
    Ensure signature images are properly base64-encoded PNG format for best compatibility.
  </Accordion>

  <Accordion title="Error Handling">
    Check both HTTP status codes and response content. Some endpoints return error messages with 200 status.
  </Accordion>

  <Accordion title="Attachment Caching">
    Leverage the `If-Modified-Since` header when repeatedly accessing the same attachment to reduce bandwidth.
  </Accordion>

  <Accordion title="Queue Processing">
    Data submitted to the queue is processed asynchronously. Implement polling or webhooks to check processing status.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Patient API" icon="user" href="/api/patients">
    Search for patients to associate with events
  </Card>

  <Card title="Authentication" icon="lock" href="/api/authentication">
    Secure your API requests
  </Card>
</CardGroup>
