Skip to content

Status Tracking

Document processing is an asynchronous process. Therefore, we need a way to track the status of the processing. There are 2 ways to track the status of the processing:

  • WebSockets (recommended)
  • Polling (alternative)

WebSockets

WebSockets are a more efficient way to track the status of the processing. It is a long-lived connection that allows you to receive updates as they happen.

WebSockets Example
import asyncio
import websockets
from pydantic import BaseModel

BASE_URL = 'ws://dev-api.v2.areal.ai/'

async def get_status(client: websockets.WebSocketClientProtocol, document_id: str) -> str:
    async with websockets.connect(f"{BASE_URL}/status/") as ws:
        while True:
            message = await ws.recv()
            msg = FileStatusUpdatedMessage.model_validate_json(message)

            # skip if not the document we are interested in
            if msg.meta.id != document_id:
                continue

            print(f"Processing {msg.meta.id} is {msg.data.status}")

            # NOTE: no need to break since if this is background task
            # and so you can keep listening for other documents

asyncio.run(get_status(document_id))

class FileStatusUpdatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        id: str
        user: User

    class Data(BaseModel):
        status: Status

    type: str = 'FILE_STATUS_UPDATED'
    meta: Meta
    data: Data
WebSockets Example
using System;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

var baseUrl = "ws://dev-api.v2.areal.ai/";
var documentId = "your_document_id_here";

using var client = new ClientWebSocket();
await client.ConnectAsync(new Uri(baseUrl + "status/"), CancellationToken.None);

var buffer = new byte[1024 * 4];

while (client.State == WebSocketState.Open)
{
    var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

    if (result.MessageType == WebSocketMessageType.Text)
    {
        var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
        var jsonDoc = JsonDocument.Parse(message);
        var root = jsonDoc.RootElement;

        var meta = root.GetProperty("meta");

        // skip if not the document we are interested in
        if (meta.GetProperty("id").GetString() != documentId)
        {
            continue;
        }

        var data = root.GetProperty("data");
        var status = data.GetProperty("status").GetString();
        Console.WriteLine($"Processing {meta.GetProperty("id").GetString()} is {status}");

        // NOTE: no need to break since if this is background task
        // and so you can keep listening for other documents
    }
    else if (result.MessageType == WebSocketMessageType.Close)
    {
        await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
    }
}
WebSockets Example
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import org.json.JSONObject;

public class WebSocketStatusTracking {
    public static void main(String[] args) throws Exception {
        String baseUrl = "ws://dev-api.v2.areal.ai/";
        String documentId = "your_document_id_here";

        HttpClient client = HttpClient.newHttpClient();
        WebSocket webSocket = client.newWebSocketBuilder()
            .buildAsync(URI.create(baseUrl + "status/"), new WebSocket.Listener() {
                @Override
                public void onOpen(WebSocket webSocket) {
                    System.out.println("WebSocket connection opened");
                    webSocket.request(1);
                    return WebSocket.Listener.super.onOpen(webSocket);
                }

                @Override
                public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
                    try {
                        JSONObject message = new JSONObject(data.toString());
                        JSONObject meta = message.getJSONObject("meta");

                        // skip if not the document we are interested in
                        if (!meta.getString("id").equals(documentId)) {
                            webSocket.request(1);
                            return null;
                        }

                        JSONObject messageData = message.getJSONObject("data");
                        String status = messageData.getString("status");
                        System.out.println("Processing " + meta.getString("id") + " is " + status);

                        // NOTE: no need to break since if this is background task
                        // and so you can keep listening for other documents
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    webSocket.request(1);
                    return null;
                }

                @Override
                public void onError(WebSocket webSocket, Throwable error) {
                    System.err.println("WebSocket error: " + error.getMessage());
                }
            })
            .join();

        // Keep the connection alive
        Thread.sleep(Long.MAX_VALUE);
    }
}

For other WebSocket notification types see WebSocket API

Polling

Continuously poll the status of the processing until it is completed or failed. You can also implement a custom timeout to avoid waiting forever.

Long Polling Example
import requests
import time

BASE_URL = 'http://dev-api.v2.areal.ai/api/v2'

# 0. Login - details in Authentication section ... client is authenticated
# 1. Starting the processing ... we get a request_id (which is actually the document_id) -- details in Processing section
# 2. Polling the status of the processing

document_id = "your_document_id_here" 

def get_status(client: requests.Session, document_id: str) -> str:
    try:
        response = client.get(f"{BASE_URL}/documents/{document_id}/")
        document_details = response.json()
        return document_details["status"]
    except Exception as e:
        print(f"Error getting status: {e}")
        return "unknown"

status = get_status(client, document_id)
# NOTE: You can also inspect other fields in document_details as needed

timeout = 10
while status not in ["completed", "failed"]:
    time.sleep(1)
    timeout -= 1
    if timeout <= 0:
        print("Timeout reached")
        break
    status = get_status(client, document_id)
Long Polling Example
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
using System.Net;
using System.Threading;

var baseUrl = "http://dev-api.v2.areal.ai/api/v2";

// 0. Login - details in Authentication section ... client is authenticated
// 1. Starting the processing ... we get a request_id (which is actually the document_id) -- details in Processing section
// 2. Polling the status of the processing

var documentId = "your_document_id_here";

var handler = new HttpClientHandler
{
    UseCookies = true,
    CookieContainer = new CookieContainer()
};
var client = new HttpClient(handler);

string status = await GetStatus(client, baseUrl, documentId);
// NOTE: You can also inspect other fields in document_details as needed

int timeout = 10;
while (status != "completed" && status != "failed")
{
    await Task.Delay(1000);
    timeout--;
    if (timeout <= 0)
    {
        Console.WriteLine("Timeout reached");
        break;
    }
    status = await GetStatus(client, baseUrl, documentId);
}

static async Task<string> GetStatus(HttpClient client, string baseUrl, string documentId)
{
    try
    {
        var response = await client.GetAsync($"{baseUrl}/documents/{documentId}/");
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var jsonDoc = JsonDocument.Parse(content);
        return jsonDoc.RootElement.GetProperty("status").GetString();
    }
    catch (Exception e)
    {
        Console.WriteLine($"Error getting status: {e.Message}");
        return "unknown";
    }
}
Long Polling Example
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.CookieManager;
import java.net.CookieHandler;
import java.nio.charset.StandardCharsets;
import org.json.JSONObject;

public class PollingStatusTracking {
    public static void main(String[] args) throws Exception {
        String baseUrl = "http://dev-api.v2.areal.ai/api/v2";

        // 0. Login - details in Authentication section ... client is authenticated
        // 1. Starting the processing ... we get a request_id (which is actually the document_id) -- details in Processing section
        // 2. Polling the status of the processing

        String documentId = "your_document_id_here";

        CookieManager cookieManager = new CookieManager();
        CookieHandler.setDefault(cookieManager);

        String status = getStatus(baseUrl, documentId);
        // NOTE: You can also inspect other fields in document_details as needed

        int timeout = 10;
        while (!status.equals("completed") && !status.equals("failed")) {
            Thread.sleep(1000);
            timeout--;
            if (timeout <= 0) {
                System.out.println("Timeout reached");
                break;
            }
            status = getStatus(baseUrl, documentId);
        }
    }

    private static String getStatus(String baseUrl, String documentId) {
        try {
            URL url = new URL(baseUrl + "/documents/" + documentId + "/");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setDoInput(true);

            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)
                );
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                reader.close();

                JSONObject documentDetails = new JSONObject(response.toString());
                return documentDetails.getString("status");
            }
            connection.disconnect();
        } catch (Exception e) {
            System.out.println("Error getting status: " + e.getMessage());
        }
        return "unknown";
    }
}

Meaning of the status

  • pending: We received your request, but processing has not started yet.
  • preparing: The document is being classified.
  • processing: Data extraction is in progress.
  • completed: Processing is complete and successful.
  • failed: Processing failed. Please check the error details or try again.

WebSocket API

Document Processing & DocPush

Document Status Tracking
from pydantic import BaseModel
from typing import Optional

# When an existing documents status changes (e.g processing -> completed)
class FileStatusUpdatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        id: str
        user: User

    class Data(BaseModel):
        status: Status

    type: str = 'FILE_STATUS_UPDATED'
    meta: Meta
    data: Data

# When a new document is created (e.g. preparing can generate multiple documents)
class FileCreatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        user: User

    type: str = 'FILE_CREATED'
    meta: Meta
    data: SessionDetailListItem

# When a document is removed 
class FileDeletedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        id: str
        user: User

    type: str = 'FILE_DELETED'
    meta: Meta
    data: None = None

class User(BaseModel):
    id: str

# When a document is pushed to a gateway (e.g. Encompass DocPush)
class DocPushFinishedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        id: str
        user: User

    class Data(BaseModel):
        status: Status
        error: Optional[str]

    type: str = 'DOC_PUSH_FINISHED'
    meta: Meta
    data: Data
Document Status Tracking
using System;
using System.Text.Json.Serialization;

// When an existing documents status changes (e.g processing -> completed)
public class FileStatusUpdatedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("status")]
        public string Status { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "FILE_STATUS_UPDATED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a new document is created (e.g. preparing can generate multiple documents)
public class FileCreatedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "FILE_CREATED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public SessionDetailListItem Data { get; set; }
}

// When a document is removed
public class FileDeletedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "FILE_DELETED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public object Data { get; set; } = null;
}

public class User
{
    [JsonPropertyName("id")]
    public string Id { get; set; }
}

// When a document is pushed to a gateway (e.g. Encompass DocPush)
public class DocPushFinishedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("status")]
        public string Status { get; set; }

        [JsonPropertyName("error")]
        public string Error { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "DOC_PUSH_FINISHED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}
Document Status Tracking
import org.json.JSONObject;

// When an existing documents status changes (e.g processing -> completed)
class FileStatusUpdatedMessage {
    public static class Meta {
        public String upload_session_id;
        public String id;
        public User user;
    }

    public static class Data {
        public String status;
    }

    public String type = "FILE_STATUS_UPDATED";
    public Meta meta;
    public Data data;
}

// When a new document is created (e.g. preparing can generate multiple documents)
class FileCreatedMessage {
    public static class Meta {
        public String upload_session_id;
        public User user;
    }

    public String type = "FILE_CREATED";
    public Meta meta;
    public SessionDetailListItem data;
}

// When a document is removed
class FileDeletedMessage {
    public static class Meta {
        public String upload_session_id;
        public String id;
        public User user;
    }

    public String type = "FILE_DELETED";
    public Meta meta;
    public Object data = null;
}

class User {
    public String id;
}

// When a document is pushed to a gateway (e.g. Encompass DocPush)
class DocPushFinishedMessage {
    public static class Meta {
        public String upload_session_id;
        public String id;
        public User user;
    }

    public static class Data {
        public String status;
        public String error;
    }

    public String type = "DOC_PUSH_FINISHED";
    public Meta meta;
    public Data data;
}

CDBalancer & CDPush/Pull

CDBalancer Status Tracking
from pydantic import BaseModel
from typing import Optional

# When the status of the CDBalancer changes (e.g. preparing -> processing -> completed)
class CDBalancerStatusUpdatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        id: str
        user: User

    class Data(BaseModel):
        status: Status

    type: str = 'CDBALANCER_STATUS_UPDATED'
    meta: Meta
    data: Data

# When a CDPull is started (e.g. Encompass CDPull)
class CDPullStartedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        user: User

    class Data(BaseModel):
        class Document(BaseModel):
            id: str
            name: str
            status: Status

        document: Document

    type: str = 'CD_PULL_STARTED'
    meta: Meta
    data: Data

# When a CDPull is finished (e.g. Encompass CDPull)
class CDPullFinishedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        user: User

    class Data(BaseModel):
        class Document(BaseModel):
            id: str
            name: str
            status: Status

        document: Optional[Document]
        status: Status
        error: Optional[str]

    type: str = 'CD_PULL_FINISHED'
    meta: Meta
    data: Data

# When a CDPush is finished (e.g. Encompass CDPush)
class CDPushFinishedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        cdbalancer_request_id: str
        gateway_request_id: Optional[str] = None
        user: User

    class Data(BaseModel):
        status: Status
        error: Optional[str]
        stats_message: Optional[str] = None
        stats_status: Optional[str] = None

    type: str = 'CD_PUSH_FINISHED'
    meta: Meta
    data: Data
CDBalancer Status Tracking
using System;
using System.Text.Json.Serialization;

// When the status of the CDBalancer changes (e.g. preparing -> processing -> completed)
public class CDBalancerStatusUpdatedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("status")]
        public string Status { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "CDBALANCER_STATUS_UPDATED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a CDPull is started (e.g. Encompass CDPull)
public class CDPullStartedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        public class Document
        {
            [JsonPropertyName("id")]
            public string Id { get; set; }

            [JsonPropertyName("name")]
            public string Name { get; set; }

            [JsonPropertyName("status")]
            public string Status { get; set; }
        }

        [JsonPropertyName("document")]
        public Document Document { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "CD_PULL_STARTED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a CDPull is finished (e.g. Encompass CDPull)
public class CDPullFinishedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        public class Document
        {
            [JsonPropertyName("id")]
            public string Id { get; set; }

            [JsonPropertyName("name")]
            public string Name { get; set; }

            [JsonPropertyName("status")]
            public string Status { get; set; }
        }

        [JsonPropertyName("document")]
        public Document Document { get; set; }

        [JsonPropertyName("status")]
        public string Status { get; set; }

        [JsonPropertyName("error")]
        public string Error { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "CD_PULL_FINISHED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a CDPush is finished (e.g. Encompass CDPush)
public class CDPushFinishedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("cdbalancer_request_id")]
        public string CDBalancerRequestId { get; set; }

        [JsonPropertyName("gateway_request_id")]
        public string GatewayRequestId { get; set; }

        [JsonPropertyName("user")]
        public User User { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("status")]
        public string Status { get; set; }

        [JsonPropertyName("error")]
        public string Error { get; set; }

        [JsonPropertyName("stats_message")]
        public string StatsMessage { get; set; }

        [JsonPropertyName("stats_status")]
        public string StatsStatus { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "CD_PUSH_FINISHED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}
CDBalancer Status Tracking
// When the status of the CDBalancer changes (e.g. preparing -> processing -> completed)
class CDBalancerStatusUpdatedMessage {
    public static class Meta {
        public String upload_session_id;
        public String id;
        public User user;
    }

    public static class Data {
        public String status;
    }

    public String type = "CDBALANCER_STATUS_UPDATED";
    public Meta meta;
    public Data data;
}

// When a CDPull is started (e.g. Encompass CDPull)
class CDPullStartedMessage {
    public static class Meta {
        public String upload_session_id;
        public User user;
    }

    public static class Data {
        public static class Document {
            public String id;
            public String name;
            public String status;
        }

        public Document document;
    }

    public String type = "CD_PULL_STARTED";
    public Meta meta;
    public Data data;
}

// When a CDPull is finished (e.g. Encompass CDPull)
class CDPullFinishedMessage {
    public static class Meta {
        public String upload_session_id;
        public User user;
    }

    public static class Data {
        public static class Document {
            public String id;
            public String name;
            public String status;
        }

        public Document document;
        public String status;
        public String error;
    }

    public String type = "CD_PULL_FINISHED";
    public Meta meta;
    public Data data;
}

// When a CDPush is finished (e.g. Encompass CDPush)
class CDPushFinishedMessage {
    public static class Meta {
        public String upload_session_id;
        public String cdbalancer_request_id;
        public String gateway_request_id;
        public User user;
    }

    public static class Data {
        public String status;
        public String error;
        public String stats_message;
        public String stats_status;
    }

    public String type = "CD_PUSH_FINISHED";
    public Meta meta;
    public Data data;
}

Copilot Tasks

Copilot Tasks Status Tracking
from pydantic import BaseModel
from typing import Optional

# When a copilot task is updated (e.g. pending -> processing -> completed)
class CopilotTaskUpdatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        task_group_id: str

    class Data(BaseModel):
        task: TaskSchema
        error: Optional[str]

    type: str = 'COPILOT_TASK_UPDATED'
    meta: Meta
    data: Data

# When a new copilot task is created
class CopilotTaskCreatedMessage(BaseModel):
    class Meta(BaseModel):
        upload_session_id: str
        task_group_id: str

    class Data(BaseModel):
        task: TaskSchema

    type: str = 'COPILOT_TASK_CREATED'
    meta: Meta
    data: Data

# When a copilot task is deleted
class CopilotTaskDeletedMessage(BaseModel):
    class Meta(BaseModel):
        task_group_id: str

    class Data(BaseModel):
        task_id: str

    type: str = 'COPILOT_TASK_DELETED'
    meta: Meta
    data: Data
Copilot Tasks Status Tracking
using System;
using System.Text.Json.Serialization;

// When a copilot task is updated (e.g. pending -> processing -> completed)
public class CopilotTaskUpdatedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("task_group_id")]
        public string TaskGroupId { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("task")]
        public TaskSchema Task { get; set; }

        [JsonPropertyName("error")]
        public string Error { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "COPILOT_TASK_UPDATED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a new copilot task is created
public class CopilotTaskCreatedMessage
{
    public class Meta
    {
        [JsonPropertyName("upload_session_id")]
        public string UploadSessionId { get; set; }

        [JsonPropertyName("task_group_id")]
        public string TaskGroupId { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("task")]
        public TaskSchema Task { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "COPILOT_TASK_CREATED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}

// When a copilot task is deleted
public class CopilotTaskDeletedMessage
{
    public class Meta
    {
        [JsonPropertyName("task_group_id")]
        public string TaskGroupId { get; set; }
    }

    public class Data
    {
        [JsonPropertyName("task_id")]
        public string TaskId { get; set; }
    }

    [JsonPropertyName("type")]
    public string Type { get; set; } = "COPILOT_TASK_DELETED";

    [JsonPropertyName("meta")]
    public Meta Meta { get; set; }

    [JsonPropertyName("data")]
    public Data Data { get; set; }
}
Copilot Tasks Status Tracking
// When a copilot task is updated (e.g. pending -> processing -> completed)
class CopilotTaskUpdatedMessage {
    public static class Meta {
        public String upload_session_id;
        public String task_group_id;
    }

    public static class Data {
        public TaskSchema task;
        public String error;
    }

    public String type = "COPILOT_TASK_UPDATED";
    public Meta meta;
    public Data data;
}

// When a new copilot task is created
class CopilotTaskCreatedMessage {
    public static class Meta {
        public String upload_session_id;
        public String task_group_id;
    }

    public static class Data {
        public TaskSchema task;
    }

    public String type = "COPILOT_TASK_CREATED";
    public Meta meta;
    public Data data;
}

// When a copilot task is deleted
class CopilotTaskDeletedMessage {
    public static class Meta {
        public String task_group_id;
    }

    public static class Data {
        public String task_id;
    }

    public String type = "COPILOT_TASK_DELETED";
    public Meta meta;
    public Data data;
}