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

# Mobile Ticketing API

> Ticket creation and dynamic contact form integration for mobile applications

This guide covers ticket creation and dynamic contact form integration for mobile applications.

## Prerequisites

Before using these APIs, configure authentication as described in [Mobile Authentication](./mobile-auth).

***

## Create Support Ticket

Creates a support ticket for issues requiring human assistance.

**Endpoint:** `POST /api/sdk/v1/tickets`

**Content-Type:** `multipart/form-data`

### Request Fields

| Field             | Type        | Required | Description                                   |
| ----------------- | ----------- | -------- | --------------------------------------------- |
| `subject`         | string      | Yes      | Ticket subject                                |
| `typology`        | string      | Yes      | Issue type: "technical", "billing", "general" |
| `priority`        | string      | Yes      | Priority: "low", "medium", "high", "urgent"   |
| `body`            | string      | Yes      | Detailed description of the issue             |
| `conversation_id` | string      | No       | UUID to link ticket to existing conversation  |
| `custom_fields`   | JSON string | No       | Array of custom field objects                 |
| `files`           | files       | No       | File attachments (max 5 files, 10MB each)     |
| `user_email`      | string      | No       | Real email for widget/anonymous users         |
| `metadata`        | JSON string | No       | Additional metadata                           |
| `ticket_form_id`  | string      | No       | Form configuration ID                         |

### Response

```json theme={null}
{
  "id": "ticket-uuid",
  "ticket_url": "https://your-domain.example.com/tickets/ticket-uuid"
}
```

### Custom Fields Format

```json theme={null}
[
  {"id": "field-id-1", "value": "selected-value"},
  {"id": "field-id-2", "value": "text-value"}
]
```

<Info>
  **File Upload Constraints:**

  | Constraint    | Value                                                             |
  | ------------- | ----------------------------------------------------------------- |
  | Max files     | 5 files per request                                               |
  | Max file size | 10MB per file                                                     |
  | Allowed types | PDF, images (PNG, JPG, JPEG), documents (DOCX, TXT), spreadsheets |
</Info>

***

## Contact Form Integration

The contact form system provides dynamic, configurable forms for ticket creation with custom fields, conditional logic, and progressive display.

### Overview

The contact form allows you to:

* Fetch dynamically configured form structures from the server
* Display custom fields based on your ticketing system configuration
* Handle conditional field visibility (fields that appear based on other field values)
* Manage hidden fields that should be submitted but not displayed
* Implement progressive disclosure (fields appear as previous fields are completed)
* Collect email addresses from anonymous widget users

***

## Get Form Configuration

Fetch the form configuration before displaying the contact form.

**Endpoint:** `GET /api/sdk/v1/config`

### Response Structure

```json theme={null}
{
  "id": "config-uuid",
  "ticketForm": {
    "id": "form-uuid",
    "name": "Support Form",
    "description": "Customer support ticket form",
    "showSubject": true,
    "showBody": true,
    "showStatus": false,
    "showPriority": true,
    "showTypology": true,
    "ticketFormFields": [
      {
        "id": "field-uuid",
        "position": 1,
        "ticketField": {
          "id": "ticket-field-uuid",
          "externalId": "custom_field_123",
          "title": "Department",
          "description": "Select your department",
          "active": true,
          "required": true,
          "isHidden": false,
          "conditionalFieldId": null,
          "conditionalFieldValue": null,
          "type": {
            "id": "type-uuid",
            "name": "tagger",
            "value": "tagger"
          },
          "ticketsFieldOptions": [
            {
              "id": "option-uuid",
              "externalId": "dept_sales",
              "name": "Sales",
              "value": "sales",
              "isDefault": false,
              "position": 1,
              "noDisplay": false
            },
            {
              "id": "option-uuid-2",
              "externalId": "dept_support",
              "name": "Technical Support",
              "value": "support",
              "isDefault": true,
              "position": 2,
              "noDisplay": false
            }
          ]
        }
      }
    ]
  }
}
```

***

## Field Types

| Type          | Description                                   |
| ------------- | --------------------------------------------- |
| `tagger`      | Dropdown select field with predefined options |
| `text`        | Single-line text input                        |
| `textarea`    | Multi-line text input                         |
| `checkbox`    | Boolean checkbox field                        |
| `multiselect` | Multiple selection dropdown                   |

***

## Field Display Logic

### Hidden Fields (`isHidden: true`)

Hidden fields should be rendered as hidden inputs. Their values must be submitted with the form, but they should not be visible to the user.

```typescript theme={null}
if (field.isHidden) {
    // Render as hidden input
    // Still include in form submission
}
```

### Conditional Fields

Fields can be conditionally displayed based on the value of another field using `conditionalFieldId` and `conditionalFieldValue`.

```typescript theme={null}
if (field.conditionalFieldId && field.conditionalFieldValue) {
    const parentFieldValue = customFields[field.conditionalFieldId];
    if (parentFieldValue !== field.conditionalFieldValue) {
        return; // Don't render this field
    }
}
```

**Example Scenario:**

* Field A: "Issue Type" (dropdown: Bug, Feature Request, Question)
* Field B: "Bug Severity" (dropdown: Low, Medium, High)
  * `conditionalFieldId`: ID of Field A
  * `conditionalFieldValue`: "Bug"
  * This field only appears when "Bug" is selected in Field A

### Progressive Field Display

Fields appear progressively based on form completion:

1. Subject field (if `showSubject` is true)
2. Typology field appears after subject is filled (if `showTypology` is true)
3. Priority field appears after typology is filled (if `showPriority` is true)
4. Custom fields appear sequentially after priority is filled
5. Each subsequent custom field appears after the previous field is completed

### Field Options

Options in select fields can have special properties:

| Property          | Description                                     |
| ----------------- | ----------------------------------------------- |
| `noDisplay: true` | Option exists but should not be shown in the UI |
| `isDefault: true` | This option should be pre-selected              |
| `position`        | Determines the order of options in the dropdown |

***

## Email Collection for Widget Users

When using widget authentication, the system creates anonymous users with email format `unknown_ago@{widget-id}.com`. To collect the user's real email:

1. Check if the user is anonymous (email starts with `unknown_ago@`)
2. Show an email input field in your form
3. Validate the email format
4. Pass the email in the `user_email` field when submitting the ticket

The real email will be stored separately and used for ticket notifications.

***

## Swift (iOS) Implementation

```swift theme={null}
// MARK: - Models

struct TicketForm: Codable {
    let id: String
    let name: String
    let showSubject: Bool
    let showBody: Bool
    let showPriority: Bool
    let showTypology: Bool
    let ticketFormFields: [TicketFormField]
}

struct TicketFormField: Codable {
    let id: String
    let position: Int
    let ticketField: TicketField
}

struct TicketField: Codable {
    let id: String
    let externalId: String?
    let title: String
    let description: String?
    let active: Bool
    let required: Bool
    let isHidden: Bool
    let conditionalFieldId: String?
    let conditionalFieldValue: String?
    let type: TicketFieldType
    let ticketsFieldOptions: [TicketFieldOption]
}

struct TicketFieldType: Codable {
    let name: String
    let value: String
}

struct TicketFieldOption: Codable {
    let id: String
    let name: String
    let value: String
    let isDefault: Bool
    let position: Int?
    let noDisplay: Bool?
}

struct CustomField: Codable {
    let id: String
    let value: String
}

// MARK: - Contact Form Manager

class ContactFormManager {
    let baseURL = "https://your-domain.example.com/api"
    var authToken: String
    var ticketForm: TicketForm?
    var customFieldValues: [String: String] = [:]

    // Fetch form configuration
    func fetchFormConfiguration() async throws -> TicketForm {
        let url = URL(string: "\(baseURL)/sdk/v1/config")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")

        let (data, _) = try await URLSession.shared.data(for: request)
        let config = try JSONDecoder().decode(ConfigResponse.self, from: data)
        self.ticketForm = config.ticketForm
        return config.ticketForm
    }

    // Check if field should be displayed
    func shouldDisplayField(_ field: TicketField, previousFieldFilled: Bool) -> Bool {
        if field.isHidden {
            return false
        }

        if let conditionalFieldId = field.conditionalFieldId,
           let conditionalValue = field.conditionalFieldValue {
            let parentValue = customFieldValues[conditionalFieldId]
            if parentValue != conditionalValue {
                return false
            }
        }

        return previousFieldFilled
    }

    // Get field identifier (prefer externalId over id)
    func getFieldIdentifier(_ field: TicketField) -> String {
        return field.externalId ?? field.id
    }

    // Get visible options for a field
    func getVisibleOptions(_ field: TicketField) -> [TicketFieldOption] {
        return field.ticketsFieldOptions
            .filter { !($0.noDisplay ?? false) }
            .sorted { ($0.position ?? 0) < ($1.position ?? 0) }
    }

    // Submit ticket with custom fields
    func submitTicket(
        subject: String,
        typology: String,
        priority: String,
        body: String,
        conversationId: String?,
        files: [Data]? = nil,
        userEmail: String? = nil,
        metadata: [String: Any]? = nil
    ) async throws -> TicketResponse {
        let url = URL(string: "\(baseURL)/sdk/v1/tickets")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")

        let boundary = UUID().uuidString
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var bodyData = Data()

        // Add basic fields
        bodyData.appendFormField(boundary: boundary, name: "subject", value: subject)
        bodyData.appendFormField(boundary: boundary, name: "typology", value: typology)
        bodyData.appendFormField(boundary: boundary, name: "priority", value: priority)
        bodyData.appendFormField(boundary: boundary, name: "body", value: body)

        // Add optional fields
        if let conversationId = conversationId {
            bodyData.appendFormField(boundary: boundary, name: "conversation_id", value: conversationId)
        }

        // Add custom fields
        if !customFieldValues.isEmpty {
            let customFields = customFieldValues.map { CustomField(id: $0.key, value: $0.value) }
            let customFieldsJSON = try JSONEncoder().encode(customFields)
            let customFieldsString = String(data: customFieldsJSON, encoding: .utf8)!
            bodyData.appendFormField(boundary: boundary, name: "custom_fields", value: customFieldsString)
        }

        // Add user email for widget users
        if let email = userEmail {
            bodyData.appendFormField(boundary: boundary, name: "user_email", value: email)
        }

        // Add ticket form ID
        if let formId = ticketForm?.id {
            bodyData.appendFormField(boundary: boundary, name: "ticket_form_id", value: formId)
        }

        // Add files
        if let files = files {
            for (index, fileData) in files.enumerated() {
                bodyData.appendFile(boundary: boundary, name: "files", filename: "file\(index).jpg", data: fileData)
            }
        }

        bodyData.append("--\(boundary)--\r\n".data(using: .utf8)!)
        request.httpBody = bodyData

        let (responseData, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(TicketResponse.self, from: responseData)
    }
}

// Helper extension for multipart form data
extension Data {
    mutating func appendFormField(boundary: String, name: String, value: String) {
        append("--\(boundary)\r\n".data(using: .utf8)!)
        append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!)
        append("\(value)\r\n".data(using: .utf8)!)
    }

    mutating func appendFile(boundary: String, name: String, filename: String, data: Data) {
        append("--\(boundary)\r\n".data(using: .utf8)!)
        append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
        append("Content-Type: application/octet-stream\r\n\r\n".data(using: .utf8)!)
        append(data)
        append("\r\n".data(using: .utf8)!)
    }
}
```

***

## Kotlin (Android) Implementation

```kotlin theme={null}
// MARK: - Models

data class TicketForm(
    val id: String,
    val name: String,
    val showSubject: Boolean,
    val showBody: Boolean,
    val showPriority: Boolean,
    val showTypology: Boolean,
    val ticketFormFields: List<TicketFormField>
)

data class TicketFormField(
    val id: String,
    val position: Int,
    val ticketField: TicketField
)

data class TicketField(
    val id: String,
    val externalId: String?,
    val title: String,
    val description: String?,
    val active: Boolean,
    val required: Boolean,
    val isHidden: Boolean,
    val conditionalFieldId: String?,
    val conditionalFieldValue: String?,
    val type: TicketFieldType,
    val ticketsFieldOptions: List<TicketFieldOption>
)

data class TicketFieldType(
    val name: String,
    val value: String
)

data class TicketFieldOption(
    val id: String,
    val name: String,
    val value: String,
    val isDefault: Boolean,
    val position: Int?,
    val noDisplay: Boolean?
)

data class CustomField(
    val id: String,
    val value: String
)

data class TicketResponse(
    val id: String,
    val ticket_url: String
)

// MARK: - Contact Form Manager

class ContactFormManager(private val authToken: String) {
    private val baseURL = "https://your-domain.example.com/api"
    private val client = OkHttpClient()
    private val gson = Gson()

    var ticketForm: TicketForm? = null
    val customFieldValues = mutableMapOf<String, String>()

    // Fetch form configuration
    suspend fun fetchFormConfiguration(): TicketForm {
        val request = Request.Builder()
            .url("$baseURL/sdk/v1/config")
            .header("Authorization", "Bearer $authToken")
            .build()

        return withContext(Dispatchers.IO) {
            val response = client.newCall(request).execute()
            val json = response.body?.string()
            val config = gson.fromJson(json, ConfigResponse::class.java)
            ticketForm = config.ticketForm
            config.ticketForm
        }
    }

    // Check if field should be displayed
    fun shouldDisplayField(field: TicketField, previousFieldFilled: Boolean): Boolean {
        if (field.isHidden) {
            return false
        }

        if (field.conditionalFieldId != null && field.conditionalFieldValue != null) {
            val parentValue = customFieldValues[field.conditionalFieldId]
            if (parentValue != field.conditionalFieldValue) {
                return false
            }
        }

        return previousFieldFilled
    }

    // Get field identifier (prefer externalId over id)
    fun getFieldIdentifier(field: TicketField): String {
        return field.externalId ?: field.id
    }

    // Get visible options for a field
    fun getVisibleOptions(field: TicketField): List<TicketFieldOption> {
        return field.ticketsFieldOptions
            .filter { !(it.noDisplay ?: false) }
            .sortedBy { it.position ?: 0 }
    }

    // Submit ticket with custom fields
    suspend fun submitTicket(
        subject: String,
        typology: String,
        priority: String,
        body: String,
        conversationId: String? = null,
        files: List<File>? = null,
        userEmail: String? = null,
        metadata: Map<String, Any>? = null
    ): TicketResponse {
        val formBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("subject", subject)
            .addFormDataPart("typology", typology)
            .addFormDataPart("priority", priority)
            .addFormDataPart("body", body)

        conversationId?.let { formBody.addFormDataPart("conversation_id", it) }

        if (customFieldValues.isNotEmpty()) {
            val customFields = customFieldValues.map { CustomField(it.key, it.value) }
            val customFieldsJSON = gson.toJson(customFields)
            formBody.addFormDataPart("custom_fields", customFieldsJSON)
        }

        userEmail?.let { formBody.addFormDataPart("user_email", it) }
        ticketForm?.id?.let { formBody.addFormDataPart("ticket_form_id", it) }

        files?.forEach { file ->
            val requestBody = file.asRequestBody("application/octet-stream".toMediaType())
            formBody.addFormDataPart("files", file.name, requestBody)
        }

        val request = Request.Builder()
            .url("$baseURL/sdk/v1/tickets")
            .header("Authorization", "Bearer $authToken")
            .post(formBody.build())
            .build()

        return withContext(Dispatchers.IO) {
            val response = client.newCall(request).execute()
            val json = response.body?.string()
            gson.fromJson(json, TicketResponse::class.java)
        }
    }
}
```

***

## Best Practices

<Tip>
  ### Form Validation

  * Validate required fields before submission
  * Show error messages clearly for each field
  * Validate email format for widget users
  * Check custom field requirements from the form configuration
</Tip>

<Tip>
  ### Progressive Disclosure

  * Implement the progressive display logic to avoid overwhelming users
  * Show fields only when previous fields are completed
  * Use conditional logic to show/hide fields based on other selections
</Tip>

<Tip>
  ### Hidden Fields

  * Always render hidden fields as hidden inputs
  * Include hidden field values in form submission
  * Don't show hidden fields in the UI but ensure their values are submitted
</Tip>

<Tip>
  ### Field Identifiers

  * Always prefer `externalId` over `id` for custom field identification
  * This ensures compatibility with external ticketing systems (Zendesk, HelpScout)
</Tip>

<Tip>
  ### Widget Users

  * Detect anonymous users (email starts with `unknown_ago@`)
  * Collect real email addresses for proper ticket notifications
  * Validate email format before submission
</Tip>

<Tip>
  ### Error Handling

  * Handle network failures gracefully
  * Show user-friendly error messages
  * Retry failed submissions
  * Validate data before submission to minimize errors
</Tip>

<Tip>
  ### User Experience

  * Pre-select default option values where specified
  * Show loading indicators during form submission
  * Provide clear success/failure feedback
  * Auto-save form data to prevent data loss
</Tip>

***

## Related Documentation

* [Mobile Authentication](./mobile-auth) - Authentication setup
* [Mobile Messaging API](./mobile-messaging) - Send and receive messages
* [Mobile SDK Examples](./mobile-sdk) - Complete implementation examples
* [Form Configuration API](./form-configuration) - Full form configuration reference
* [Ticketing API](./ticketing-api) - Complete ticketing API reference
