# Taskgroups

**Audience:** Developers

## Taskgroups API Contract

### Overview

The Taskgroups API provides endpoints for managing groups of related research tasks. Taskgroups allow you to create multiple tasks with shared configuration (profile and webhook), track group-level status computed from member tasks, and receive webhook notifications for group status changes.

**Authentication:** All endpoints require the `X-API-Key` header with either:

* Account API key (from account creation)
* Master API key (from `GL_DEEP_RESEARCH_MASTER_API_KEY` environment variable)

**Key Features:**

* **Shared Configuration**: All tasks in a group share the same profile and webhook
* **Flexible Query Input**: Create groups with single query, multiple queries, or no queries (empty group)
* **Computed Status**: Group status is calculated from member task statuses
* **Webhook Support**: Receive notifications when group status changes (only on status transitions, not on GET requests)

***

### Create Taskgroup (`POST /v1/taskgroups`)

Create a new taskgroup with one or more research tasks. All tasks in the group will share the same profile and webhook configuration.

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'query=What are the latest developments in quantum computing?' \
--data-urlencode 'profile=GPTR-QUICK' \
--data-urlencode 'webhook={"url": "https://webhook.site/your-unique-id", "secret": "your-secret"}'
```

**Request Fields:**

| Field     | Type            | Required | Description                                                                                                                                                                                                                                                                                                   |
| --------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`   | string \| array | No       | Query or list of queries. If string, creates one task. If array, creates one task per query. If omitted, creates an empty group (no tasks).                                                                                                                                                                   |
| `profile` | string          | Yes      | Profile name to use for research configuration. The value represents the profile name, e.g., "TONGYI", "GPTR-QUICK", "GPTR-DEEP"                                                                                                                                                                              |
| `webhook` | string          | No       | Optional webhook config as JSON: `{"url": "...", "secret": "..."}`. Both `url` and `secret` are optional. Webhook will be called when group status changes to `STARTED`, `SUCCESS`, `FAILURE`, or `PARTIAL_FAILURE`. If `secret` is provided, requests include `X-Webhook-Signature` header for verification. |

**Note:** The request must be sent as `application/x-www-form-urlencoded` form data.

**Query Parameter Formats:**

1. **Single Query (String):**

   ```bash
   --data-urlencode 'query=What is AI?'
   ```

   Creates one task with the query.
2. **Multiple Queries (JSON Array):**

   ```bash
   --data-urlencode 'query=["What is AI?", "What is machine learning?", "What is deep learning?"]'
   ```

   Creates one task per query in the array.
3. **Empty Group (Omit query):**

   ```bash
   # Don't include query parameter
   --data-urlencode 'profile=GPTR-QUICK'
   ```

   Creates a taskgroup with no tasks (status will be `EMPTY`).

**Example Success Response (202 Accepted):**

```json
{
  "taskgroup_id": "your-taskgroup-id",
  "created_at": "2024-12-02T10:00:00Z",
  "tasks": [
    "your-task-id",
    "550e8400-e29b-41d4-a716-446655440002"
  ],
  "errors": null
}
```

**Example Success Response with Partial Failures (202 Accepted):**

If some tasks succeed and others fail during creation:

```json
{
  "taskgroup_id": "your-taskgroup-id",
  "created_at": "2024-12-02T10:00:00Z",
  "tasks": [
    "your-task-id"
  ],
  "errors": [
    "Failed to create task for query 'Invalid query': Profile not found"
  ]
}
```

**Response Fields:**

| Field          | Type                     | Description                                                        |
| -------------- | ------------------------ | ------------------------------------------------------------------ |
| `taskgroup_id` | string                   | Unique identifier for the created taskgroup                        |
| `created_at`   | datetime (ISO 8601)      | Timestamp when taskgroup was created                               |
| `tasks`        | array of strings         | List of created task IDs (empty if no tasks created)               |
| `errors`       | array of strings \| null | List of errors if any task creation failed (null if all succeeded) |

**Error Handling:**

* **400 Bad Request**: Invalid query format, invalid webhook config, or invalid profile
* **404 Not Found**: Profile not found
* **500 Internal Server Error**: Server error

**Webhook Behavior:**

* Webhooks are only sent when the group status changes (not on GET requests)
* Webhooks are triggered when status transitions to: `STARTED`, `SUCCESS`, `FAILURE`, or `PARTIAL_FAILURE`
* Webhook payload matches the `GET /v1/taskgroups/{taskgroup_id}` response format
* If `secret` is provided, webhook requests include `X-Webhook-Signature` header for verification

**Note:** Tasks within a group use the group's webhook configuration. Attempting to customize individual task webhooks will result in a validation error.

***

### Get Taskgroup (`GET /v1/taskgroups/{taskgroup_id}`)

Retrieve taskgroup information including computed status, profile, webhook configuration, and list of task IDs.

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id' \
--header 'X-API-Key: your-api-key'
```

**Example Success Response (200 OK):**

```json
{
  "taskgroup_id": "your-taskgroup-id",
  "status": "SUCCESS",
  "profile": "GPTR-QUICK",
  "created_at": "2024-12-02T10:00:00Z",
  "updated_at": "2024-12-02T10:05:00Z",
  "webhook": {
    "url": "https://webhook.site/your-unique-id"
  },
  "tasks": [
    "your-task-id",
    "550e8400-e29b-41d4-a716-446655440002",
    "550e8400-e29b-41d4-a716-446655440003"
  ]
}
```

**Example Error Response (404 Not Found - Taskgroup Not Found):**

```json
{
  "detail": "Taskgroup 'your-taskgroup-id' not found"
}
```

**Response Fields:**

| Field          | Type                        | Description                                                                                          |
| -------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `taskgroup_id` | string                      | Unique identifier for the taskgroup                                                                  |
| `status`       | string                      | Taskgroup status: `"EMPTY"`, `"PENDING"`, `"STARTED"`, `"SUCCESS"`, `"FAILURE"`, `"PARTIAL_FAILURE"` |
| `profile`      | string                      | Profile name used for research configuration                                                         |
| `created_at`   | datetime (ISO 8601)         | Timestamp when taskgroup was created                                                                 |
| `updated_at`   | datetime (ISO 8601) \| null | Timestamp when taskgroup was last updated (null if never updated)                                    |
| `webhook`      | object \| null              | Webhook configuration (URL only, secret excluded for security). Null if no webhook configured.       |
| `webhook.url`  | string                      | Webhook URL (only if webhook is configured)                                                          |
| `tasks`        | array of strings            | List of task IDs in the group                                                                        |

**Status Values:**

* `"EMPTY"`: Taskgroup has no tasks (default status for empty groups)
* `"PENDING"`: At least one task is pending, but no tasks have started yet
* `"STARTED"`: At least one task has started (or is in a running state: `RECEIVED`, `RETRY`)
* `"SUCCESS"`: All tasks completed successfully
* `"FAILURE"`: All tasks failed
* `"PARTIAL_FAILURE"`: Some tasks succeeded, some failed

**Status Calculation Priority:**

1. If no tasks → `EMPTY`
2. If at least one task is `STARTED`, `RECEIVED`, or `RETRY` → `STARTED`
3. Else if at least one task is `PENDING` → `PENDING`
4. Else if at least one task is `FAILURE` → `PARTIAL_FAILURE`
5. Else if all tasks are `FAILURE` → `FAILURE`
6. Else if all tasks are `SUCCESS` → `SUCCESS`
7. Else → `PENDING`

**Note:**

* Status is computed on-demand from member task statuses
* Status is updated in the database when `GET /v1/taskgroups/{taskgroup_id}` is called
* **Webhooks are NOT sent when calling GET endpoint** - they are only sent when status changes occur due to task updates

**Error Handling:**

* **400 Bad Request**: Invalid `taskgroup_id` format
* **404 Not Found**: Taskgroup not found
* **500 Internal Server Error**: Server error

***

### Add Task to Taskgroup (`POST /v1/taskgroups/{taskgroup_id}/tasks`)

Add a new task to an existing taskgroup. The task inherits the group's profile and webhook configuration.

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id/tasks' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'query=Additional research query'
```

**Request Fields:**

| Field   | Type   | Required | Description                                             |
| ------- | ------ | -------- | ------------------------------------------------------- |
| `query` | string | Yes      | The research query for the new task (minimum length: 1) |

**Inherited Configuration:**

* **Profile:** Inherits from the taskgroup
* **Webhook:** Inherits from the taskgroup (if configured)

**Example Success Response (202 Accepted):**

```json
{
  "task_id": "your-task-id",
  "taskgroup_id": "your-taskgroup-id",
  "created_at": "2024-12-02T10:00:00Z",
  "error": ""
}
```

**Response Fields:**

| Field          | Type                | Description                                            |
| -------------- | ------------------- | ------------------------------------------------------ |
| `task_id`      | string              | Unique identifier for the created task                 |
| `taskgroup_id` | string              | ID of the taskgroup the task was added to              |
| `created_at`   | datetime (ISO 8601) | Timestamp when task was created                        |
| `error`        | string              | Error message if creation failed (empty if successful) |

**Error Handling:**

* **400 Bad Request**: Invalid taskgroup\_id format or empty query
* **404 Not Found**: Taskgroup not found
* **500 Internal Server Error**: Server error

***

### Get Task from Taskgroup (`GET /v1/taskgroups/{taskgroup_id}/tasks/{task_id}`)

Retrieve a specific task from a taskgroup. This is equivalent to `GET /v1/tasks/{task_id}` but validates that the task belongs to the specified group.

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id/tasks/your-task-id' \
--header 'X-API-Key: your-api-key'
```

**Example Success Response (200 OK):**

```json
{
  "task_id": "your-task-id",
  "taskgroup_id": "your-taskgroup-id",
  "status": "SUCCESS",
  "data": {
    "query": "What are the latest developments in quantum computing?",
    "result": "Research findings...",
    "profile": "GPTR-QUICK",
    "created_at": "2024-12-02T10:00:00Z",
    "completed_at": "2024-12-02T10:00:45Z",
    "duration": 45.23
  },
  "error": ""
}
```

**Response Fields:**

| Field          | Type           | Description                                                                                           |
| -------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
| `task_id`      | string         | Unique identifier for the task                                                                        |
| `taskgroup_id` | string \| null | ID of the taskgroup if the task belongs to one (null for standalone tasks)                            |
| `status`       | string         | Task status: `"PENDING"`, `"RECEIVED"`, `"STARTED"`, `"SUCCESS"`, `"FAILURE"`, `"REVOKED"`, `"RETRY"` |
| `data`         | object \| null | Research result data (null if status is not "SUCCESS")                                                |
| `error`        | string         | Error message if task failed (empty string if successful)                                             |

**Error Handling:**

* **400 Bad Request**: Invalid taskgroup\_id or task\_id format
* **404 Not Found**: Taskgroup not found or task does not belong to group
* **500 Internal Server Error**: Server error

***

### Get Taskgroup Stream (`GET /v1/taskgroups/{taskgroup_id}/stream`)

Get compiled streaming events from all tasks in a taskgroup. Events are streamed sequentially from each task in the group using Server-Sent Events (SSE).

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id/stream' \
--header 'X-API-Key: your-api-key'
```

**Response:** Server-Sent Events (SSE) stream containing events from all tasks in the group, delivered sequentially.

**Error Handling:**

* **400 Bad Request**: Invalid taskgroup\_id format
* **404 Not Found**: Taskgroup not found
* **500 Internal Server Error**: Server error

***

### Get Task Stream from Taskgroup (`GET /v1/taskgroups/{taskgroup_id}/tasks/{task_id}/stream`)

Get streaming events for a specific task in a taskgroup. This is equivalent to `GET /v1/tasks/{task_id}/stream` but validates that the task belongs to the specified group.

**Authentication:** API key required (account or master key)

**Example cURL:**

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id/tasks/your-task-id/stream' \
--header 'X-API-Key: your-api-key'
```

**Response:** Server-Sent Events (SSE) stream containing the task's events.

**Error Handling:**

* **400 Bad Request**: Invalid taskgroup\_id or task\_id format
* **404 Not Found**: Taskgroup not found or task does not belong to group
* **500 Internal Server Error**: Server error

***

### Use Cases

**Batch Processing:** Create multiple related research queries with shared configuration:

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'query=["What is AI?", "What is machine learning?", "What is deep learning?"]' \
--data-urlencode 'profile=GPTR-QUICK'
```

**Project Organization:** Organize tasks by project or research topic with consistent configuration:

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'query=Research quantum computing applications' \
--data-urlencode 'profile=GPTR-DEEP' \
--data-urlencode 'webhook={"url": "https://my-project.com/webhook"}'
```

**Monitoring Progress:** Track the overall progress of related research tasks:

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup/your-taskgroup-id' \
--header 'X-API-Key: your-api-key'
```

**Empty Group Creation:** Create a taskgroup first, then add tasks later (if needed):

```bash
curl --location 'https://stag-gl-deep-research.obrol.id/v1/taskgroup' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'profile=GPTR-QUICK'
```

***

### Webhook Integration

Taskgroups support webhook notifications when the group status changes. Webhooks are only sent when status transitions occur (not on GET requests).

**Webhook Trigger Events:**

* Status changes to `STARTED`
* Status changes to `SUCCESS`
* Status changes to `FAILURE`
* Status changes to `PARTIAL_FAILURE`

**Webhook Payload:**

The webhook payload matches the `GET /v1/taskgroups/{taskgroup_id}` response format:

```json
{
  "taskgroup_id": "your-taskgroup-id",
  "status": "SUCCESS",
  "profile": "GPTR-QUICK",
  "created_at": "2024-12-02T10:00:00Z",
  "updated_at": "2024-12-02T10:05:00Z",
  "webhook": {
    "url": "https://webhook.site/your-unique-id"
  },
  "tasks": [
    "your-task-id",
    "550e8400-e29b-41d4-a716-446655440002"
  ]
}
```

**Webhook Signature Verification:**

If a `secret` is provided when creating the taskgroup, webhook requests will include an `X-Webhook-Signature` header for verification. The signature is computed using HMAC-SHA256 with the webhook secret.

**Example Webhook Verification (Python):**

```python
import hmac
import hashlib
import json

def verify_webhook_signature(payload: dict, signature: str, secret: str) -> bool:
    """Verify webhook signature."""
    payload_str = json.dumps(payload, sort_keys=True)
    expected_signature = hmac.new(
        secret.encode(),
        payload_str.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_signature, signature)
```

***

### Relationship with Tasks API

Taskgroups are built on top of the Tasks API. Each task in a group is created using the same logic as `POST /v1/tasks`, but with the following differences:

1. **Shared Configuration**: All tasks in a group share the same profile and webhook
2. **Group Status**: Group status is computed from member task statuses
3. **Webhook Behavior**: Group webhooks are sent for group status changes, not individual task status changes

**Individual Task Access:**

You can still access individual tasks using the Tasks API:

* `GET /v1/tasks/{task_id}` - Get full task result
* `GET /v1/tasks/{task_id}/status` - Get task status
* `GET /v1/tasks/{task_id}/stream` - Get task streaming events

**Taskgroup vs Individual Tasks:**

* Use **Taskgroups** when you want to manage multiple related tasks with shared configuration
* Use **Individual Tasks** when you need fine-grained control over each task's configuration

***

### Cleanup and Retention

Taskgroups are automatically cleaned up based on the following criteria:

* **Cleanup Conditions**: Taskgroups with status `SUCCESS`, `FAILURE`, `PARTIAL_FAILURE`, or `EMPTY` that have `updated_at` older than the retention period (default: 24 hours, configurable via `TASK_RESULT_EXPIRES_HOURS`)
* **Cleanup Process**: Runs automatically as part of the main application process
* **Retention Period**: Configurable via `TASK_RESULT_EXPIRES_HOURS` environment variable

**Note:** Taskgroups with status `PENDING` or `STARTED` are not cleaned up, even if they exceed the retention period.
