> ## Documentation Index
> Fetch the complete documentation index at: https://webscraping.titannet.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use the Task Service API

> Task Service is Titan’s primary public API for tasks, executions, templates, results, datasets, and media—classic extraction and modular workflows alike.

If you are building a backend integration, internal tool, agent runtime, or custom frontend, this is the service you will use most often. It is the user-facing control plane for **task definitions** (including **modular** search, crawl, scrape, and API-call steps), **execution** lifecycle, **templates**, **results**, **datasets**, and stable **media** access.

<Note>
  **Rust** snippets use **ureq** and **serde\_json** (`ureq = { version = "2", features = ["json"] }`, `serde_json = "1"`). **Go** snippets use the standard library only. Tab titles match [Authentication](/get-started/authentication-and-api-keys) and the [Quickstart](/get-started/quickstart-run-your-first-task) so your language choice stays in sync.
</Note>

## What you will build

In this guide, you will build a minimal programmatic integration that can:

1. Authenticate with a bearer token
2. Create a task
3. Trigger execution
4. Poll execution status
5. Export results

## Before you begin

| Requirement           | Example                                                                         |
| --------------------- | ------------------------------------------------------------------------------- |
| Task Service base URL | `https://api.webscraping.titannet.io` (no `/api/v1` suffix in env vars below)   |
| Bearer token          | JWT or API key                                                                  |
| Required scopes       | `tasks:read`, `tasks:write`, `executions:read`, `executions:write`, `data:read` |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    export TITAN_API_URL="https://api.webscraping.titannet.io"
    export TITAN_TOKEN="your-bearer-token"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // os.Setenv("TITAN_API_URL", "https://api.webscraping.titannet.io")
    // os.Setenv("TITAN_TOKEN", "your-bearer-token")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    process.env.TITAN_API_URL ??= "https://api.webscraping.titannet.io";
    process.env.TITAN_TOKEN ??= "your-bearer-token";
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os

    os.environ.setdefault("TITAN_API_URL", "https://api.webscraping.titannet.io")
    os.environ.setdefault("TITAN_TOKEN", "your-bearer-token")
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    // std::env::set_var("TITAN_API_URL", "https://api.webscraping.titannet.io");
    // std::env::set_var("TITAN_TOKEN", "your-bearer-token");
    ```
  </Tab>
</Tabs>

## How Task Service fits into the platform

```mermaid theme={null}
flowchart LR
    User -->|Bearer token| TaskService[Task Service API]
    TaskService --> Tasks
    TaskService --> Executions
    TaskService --> Templates
    TaskService --> Results
    TaskService --> Datasets
    TaskService --> Media
```

## Response model

Most JSON endpoints return a **success envelope** with the typed payload under **`data`**:

```json theme={null}
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "active",
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-01T00:00:00Z"
  }
}
```

Errors use **`success: false`** and an **`error`** object with **`code`** and **`message`** (see [API reference overview](/api-reference/overview) and [HTTP errors and exceptions](/api-reference/errors)).

When building a client, **parse and check the envelope first**, then read fields from **`data`**. A few routes (for example **`GET …/results/export`**) stream **files** instead of JSON envelopes—treat those as raw HTTP bodies.

## Main endpoint groups

| Capability        | Common endpoint group                                              |
| ----------------- | ------------------------------------------------------------------ |
| Task management   | `/api/v1/tasks`                                                    |
| Execution control | `/api/v1/executions`                                               |
| Templates         | `/api/v1/templates`                                                |
| Result access     | `/api/v1/executions/:id/results`                                   |
| Dataset access    | `/api/v1/tasks/:id/datasets` and `/api/v1/executions/:id/datasets` |
| Media access      | `/api/v1/executions/:id/media`                                     |

## Build a minimal client

These helpers assume **`TITAN_API_URL`** is the **origin** (for example `https://api.webscraping.titannet.io`) and append **`/api/v1`**.

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    # JSON body POST
    titan_post() {
      local path="$1"
      shift
      curl -sS -X POST "${TITAN_API_URL}/api/v1${path}" \
        -H "Authorization: Bearer ${TITAN_TOKEN}" \
        -H "Content-Type: application/json" \
        -d "$@"
    }

    # GET with bearer
    titan_get() {
      curl -sS "${TITAN_API_URL}/api/v1$1" \
        -H "Authorization: Bearer ${TITAN_TOKEN}"
    }
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    func titanJSON(method, path string, body any, token, base string) ([]byte, error) {
      var rdr io.Reader
      if body != nil {
        b, err := json.Marshal(body)
        if err != nil {
          return nil, err
        }
        rdr = bytes.NewReader(b)
      }
      req, err := http.NewRequest(method, base+"/api/v1"+path, rdr)
      if err != nil {
        return nil, err
      }
      req.Header.Set("Authorization", "Bearer "+token)
      if body != nil {
        req.Header.Set("Content-Type", "application/json")
      }
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
        return nil, err
      }
      defer resp.Body.Close()
      return io.ReadAll(resp.Body)
    }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;

    type SuccessEnvelope<T> = { success: true; data: T };
    type ErrorEnvelope = {
      success: false;
      error: { code: string; message: string };
    };

    async function titanJson<T>(res: Response): Promise<T> {
      const body = (await res.json()) as SuccessEnvelope<T> | ErrorEnvelope;
      if (!res.ok || body.success === false) {
        throw new Error(`HTTP ${res.status}: ${JSON.stringify(body)}`);
      }
      return body.data;
    }

    async function titanRequest<T>(
      method: string,
      path: string,
      body?: unknown,
    ): Promise<T> {
      const res = await fetch(`${base}/api/v1${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${token}`,
          ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
        },
        body: body !== undefined ? JSON.stringify(body) : undefined,
      });
      return titanJson<T>(res);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import json
    import os
    import urllib.error
    import urllib.request

    def titan_request(method: str, path: str, body: dict | None = None) -> dict:
        base = os.environ["TITAN_API_URL"]
        token = os.environ["TITAN_TOKEN"]
        data = None if body is None else json.dumps(body).encode()
        req = urllib.request.Request(
            f"{base}/api/v1{path}",
            data=data,
            headers={
                "Authorization": f"Bearer {token}",
                **({"Content-Type": "application/json"} if body is not None else {}),
            },
            method=method,
        )
        with urllib.request.urlopen(req) as resp:
            payload = json.loads(resp.read().decode())
        if not payload.get("success", False):
            raise RuntimeError(payload)
        return payload["data"]
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::Value;

    fn titan_get_json(path: &str) -> Result<Value, Box<dyn std::error::Error>> {
        let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
        let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
        let resp = ureq::get(format!("{base}/api/v1{path}"))
            .set("Authorization", &format!("Bearer {token}"))
            .call().expect("http");
        let v: Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
        if v["success"] != true {
            return Err(format!("{v}").into());
        }
        Ok(v["data"].clone())
    }

    fn titan_post_json(path: &str, body: Value) -> Result<Value, Box<dyn std::error::Error>> {
        let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
        let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
        let resp = ureq::post(format!("{base}/api/v1{path}"))
            .set("Authorization", &format!("Bearer {token}"))
            .set("Content-Type", "application/json")
            .send_json(body).expect("http");
        let v: Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
        if v["success"] != true {
            return Err(format!("{v}").into());
        }
        Ok(v["data"].clone())
    }
    ```
  </Tab>
</Tabs>

## Create a task programmatically

The table below describes the **classic extraction** shape most teams start with: known URLs plus an objective and output schema. Titan also supports **modular** and **multi-step** tasks where `urls`, `output_schema`, and other fields are expressed **per action** or inside an `execution_plan` instead of only at the top level. For those patterns, see [Action types overview](/about-platform/action-types/overview) and the **Task Service** operations in the API Reference tab.

| Field            | Required (classic)  | Meaning                                      |
| ---------------- | ------------------- | -------------------------------------------- |
| `name`           | Yes                 | Human-readable task label                    |
| `urls`           | Yes                 | Target URLs                                  |
| `objective`      | Yes                 | What the run should accomplish               |
| `output_schema`  | Yes                 | JSON schema describing the structured result |
| `execution_type` | Yes                 | `single` or `scheduled`                      |
| `schedule`       | For scheduled tasks | Cron string such as `0 * * * *`              |

### Example: create a single-run task

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS -X POST "$TITAN_API_URL/api/v1/tasks" \
      -H "Authorization: Bearer $TITAN_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Product extraction",
        "urls": ["https://example.com/product/1"],
        "objective": "Extract product details",
        "output_schema": {
          "type": "object",
          "properties": {
            "title": { "type": "string" },
            "price": { "type": "string" }
          }
        },
        "execution_type": "single"
      }'
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    body := map[string]any{
      "name":        "Product extraction",
      "urls":        []string{"https://example.com/product/1"},
      "objective":   "Extract product details",
      "output_schema": map[string]any{
        "type": "object",
        "properties": map[string]any{
          "title": map[string]string{"type": "string"},
          "price": map[string]string{"type": "string"},
        },
      },
      "execution_type": "single",
    }
    b, _ := json.Marshal(body)
    req, _ := http.NewRequest("POST", os.Getenv("TITAN_API_URL")+"/api/v1/tasks", bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    req.Header.Set("Content-Type", "application/json")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const res = await fetch(`${base}/api/v1/tasks`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Product extraction",
        urls: ["https://example.com/product/1"],
        objective: "Extract product details",
        output_schema: {
          type: "object",
          properties: {
            title: { type: "string" },
            price: { type: "string" },
          },
        },
        execution_type: "single",
      }),
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import json
    import os
    import urllib.request

    payload = {
        "name": "Product extraction",
        "urls": ["https://example.com/product/1"],
        "objective": "Extract product details",
        "output_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "price": {"type": "string"},
            },
        },
        "execution_type": "single",
    }
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/tasks",
        data=json.dumps(payload).encode(),
        headers={
            "Authorization": f"Bearer {os.environ['TITAN_TOKEN']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::json;

    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let body = json!({
        "name": "Product extraction",
        "urls": ["https://example.com/product/1"],
        "objective": "Extract product details",
        "output_schema": {
            "type": "object",
            "properties": {
                "title": { "type": "string" },
                "price": { "type": "string" }
            }
        },
        "execution_type": "single"
    });
    let resp = ureq::post(format!("{base}/api/v1/tasks"))
        .set("Authorization", &format!("Bearer {token}"))
        .set("Content-Type", "application/json")
        .send_json(body).expect("http");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

### Example: create a scheduled task

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS -X POST "$TITAN_API_URL/api/v1/tasks" \
      -H "Authorization: Bearer $TITAN_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Hourly price monitor",
        "urls": ["https://example.com/product/1"],
        "objective": "Track the current price",
        "output_schema": {
          "type": "object",
          "properties": {
            "price": { "type": "string" },
            "timestamp": { "type": "string" }
          }
        },
        "execution_type": "scheduled",
        "schedule": "0 * * * *"
      }'
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    body := map[string]any{
      "name":      "Hourly price monitor",
      "urls":      []string{"https://example.com/product/1"},
      "objective": "Track the current price",
      "output_schema": map[string]any{
        "type": "object",
        "properties": map[string]any{
          "price":     map[string]string{"type": "string"},
          "timestamp": map[string]string{"type": "string"},
        },
      },
      "execution_type": "scheduled",
      "schedule":       "0 * * * *",
    }
    b, _ := json.Marshal(body)
    req, _ := http.NewRequest("POST", os.Getenv("TITAN_API_URL")+"/api/v1/tasks", bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    req.Header.Set("Content-Type", "application/json")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const res = await fetch(`${base}/api/v1/tasks`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Hourly price monitor",
        urls: ["https://example.com/product/1"],
        objective: "Track the current price",
        output_schema: {
          type: "object",
          properties: {
            price: { type: "string" },
            timestamp: { type: "string" },
          },
        },
        execution_type: "scheduled",
        schedule: "0 * * * *",
      }),
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import json
    import os
    import urllib.request

    payload = {
        "name": "Hourly price monitor",
        "urls": ["https://example.com/product/1"],
        "objective": "Track the current price",
        "output_schema": {
            "type": "object",
            "properties": {
                "price": {"type": "string"},
                "timestamp": {"type": "string"},
            },
        },
        "execution_type": "scheduled",
        "schedule": "0 * * * *",
    }
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/tasks",
        data=json.dumps(payload).encode(),
        headers={
            "Authorization": f"Bearer {os.environ['TITAN_TOKEN']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::json;

    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let body = json!({
        "name": "Hourly price monitor",
        "urls": ["https://example.com/product/1"],
        "objective": "Track the current price",
        "output_schema": {
            "type": "object",
            "properties": {
                "price": { "type": "string" },
                "timestamp": { "type": "string" }
            }
        },
        "execution_type": "scheduled",
        "schedule": "0 * * * *"
    });
    let resp = ureq::post(format!("{base}/api/v1/tasks"))
        .set("Authorization", &format!("Bearer {token}"))
        .set("Content-Type", "application/json")
        .send_json(body).expect("http");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

## Trigger and monitor execution

| Pattern                    | Endpoint                     | Best for                        |
| -------------------------- | ---------------------------- | ------------------------------- |
| Run saved task             | `POST /api/v1/tasks/:id/run` | Standard user workflows         |
| Trigger execution directly | `POST /api/v1/executions`    | Execution-oriented integrations |

### Run a saved task

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS -X POST "$TITAN_API_URL/api/v1/tasks/$TASK_ID/run" \
      -H "Authorization: Bearer $TITAN_TOKEN"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest(
      "POST",
      fmt.Sprintf("%s/api/v1/tasks/%s/run", os.Getenv("TITAN_API_URL"), os.Getenv("TASK_ID")),
      nil,
    )
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const taskId = process.env.TASK_ID!;
    const res = await fetch(`${base}/api/v1/tasks/${taskId}/run`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    tid = os.environ["TASK_ID"]
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/tasks/{tid}/run",
        headers={"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let task_id = std::env::var("TASK_ID").expect("TASK_ID");
    let resp = ureq::post(format!("{base}/api/v1/tasks/{task_id}/run"))
        .set("Authorization", &format!("Bearer {token}"))
        .send_json(serde_json::json!({})).expect("http");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

The JSON response wraps an **`ExecutionResponse`** in **`data`**; use **`data.id`** as the execution UUID for later `GET /api/v1/executions/:id` calls.

### Poll execution state

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS "$TITAN_API_URL/api/v1/executions/$EXECUTION_ID" \
      -H "Authorization: Bearer $TITAN_TOKEN"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest(
      "GET",
      fmt.Sprintf("%s/api/v1/executions/%s", os.Getenv("TITAN_API_URL"), os.Getenv("EXECUTION_ID")),
      nil,
    )
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const executionId = process.env.EXECUTION_ID!;
    const res = await fetch(`${base}/api/v1/executions/${executionId}`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    eid = os.environ["EXECUTION_ID"]
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/executions/{eid}",
        headers={"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let execution_id = std::env::var("EXECUTION_ID").expect("EXECUTION_ID");
    let resp = ureq::get(format!("{base}/api/v1/executions/{execution_id}"))
        .set("Authorization", &format!("Bearer {token}"))
        .call().expect("http");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

Read **`data.status`** from the envelope on each poll.

## Export results

When the execution reaches a terminal status such as **`completed`**, switch from execution monitoring to result access.

### Result metadata (JSON envelope)

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS "$TITAN_API_URL/api/v1/executions/$EXECUTION_ID/results" \
      -H "Authorization: Bearer $TITAN_TOKEN"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest(
      "GET",
      fmt.Sprintf("%s/api/v1/executions/%s/results", os.Getenv("TITAN_API_URL"), os.Getenv("EXECUTION_ID")),
      nil,
    )
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const executionId = process.env.EXECUTION_ID!;
    const res = await fetch(`${base}/api/v1/executions/${executionId}/results`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    eid = os.environ["EXECUTION_ID"]
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/executions/{eid}/results",
        headers={"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let execution_id = std::env::var("EXECUTION_ID").expect("EXECUTION_ID");
    let resp = ureq::get(format!("{base}/api/v1/executions/{execution_id}/results"))
        .set("Authorization", &format!("Bearer {token}"))
        .call().expect("http");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

### Export as JSON file (raw body, no envelope)

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS "$TITAN_API_URL/api/v1/executions/$EXECUTION_ID/results/export" \
      -H "Authorization: Bearer $TITAN_TOKEN" \
      -o results.json
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest(
      "GET",
      fmt.Sprintf("%s/api/v1/executions/%s/results/export", os.Getenv("TITAN_API_URL"), os.Getenv("EXECUTION_ID")),
      nil,
    )
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    os.WriteFile("results.json", out, 0o644)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const executionId = process.env.EXECUTION_ID!;
    const res = await fetch(`${base}/api/v1/executions/${executionId}/results/export`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    const fs = await import("node:fs/promises");
    await fs.writeFile("results.json", Buffer.from(await res.arrayBuffer()));
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    eid = os.environ["EXECUTION_ID"]
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/executions/{eid}/results/export",
        headers={"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp, open("results.json", "wb") as f:
        f.write(resp.read())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use std::io::Read;

    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let execution_id = std::env::var("EXECUTION_ID").expect("EXECUTION_ID");
    let resp = ureq::get(format!("{base}/api/v1/executions/{execution_id}/results/export"))
        .set("Authorization", &format!("Bearer {token}"))
        .call().expect("http");
    let mut body = Vec::new();
    resp.into_reader().read_to_end(&mut body).expect("read");
    std::fs::write("results.json", body).expect("write");
    ```
  </Tab>
</Tabs>

### Download as CSV

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS "$TITAN_API_URL/api/v1/executions/$EXECUTION_ID/results/download" \
      -H "Authorization: Bearer $TITAN_TOKEN" -o results.csv
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest(
      "GET",
      fmt.Sprintf("%s/api/v1/executions/%s/results/download", os.Getenv("TITAN_API_URL"), os.Getenv("EXECUTION_ID")),
      nil,
    )
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    os.WriteFile("results.csv", out, 0o644)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    const executionId = process.env.EXECUTION_ID!;
    const res = await fetch(`${base}/api/v1/executions/${executionId}/results/download`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    const fs = await import("node:fs/promises");
    await fs.writeFile("results.csv", Buffer.from(await res.arrayBuffer()));
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    eid = os.environ["EXECUTION_ID"]
    req = urllib.request.Request(
        f"{os.environ['TITAN_API_URL']}/api/v1/executions/{eid}/results/download",
        headers={"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp, open("results.csv", "wb") as f:
        f.write(resp.read())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use std::io::Read;

    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");
    let execution_id = std::env::var("EXECUTION_ID").expect("EXECUTION_ID");
    let resp = ureq::get(format!("{base}/api/v1/executions/{execution_id}/results/download"))
        .set("Authorization", &format!("Bearer {token}"))
        .call().expect("http");
    let mut body = Vec::new();
    resp.into_reader().read_to_end(&mut body).expect("read");
    std::fs::write("results.csv", body).expect("write");
    ```
  </Tab>
</Tabs>

## A full client flow

Copy the **Build a minimal client** snippet for your language, then run the flow below (same `titanRequest` / `titan_json` names where applicable).

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    # Requires titan_post / titan_get from "Build a minimal client" (cURL tab).
    TASK_ID=$(titan_post /tasks "$(cat <<'JSON'
    {
      "name": "My task",
      "urls": ["https://example.com"],
      "objective": "Extract data",
      "output_schema": { "type": "object", "properties": { "title": { "type": "string" } } },
      "execution_type": "single"
    }
    JSON
    )" | jq -r '.data.id')

    RUN_JSON=$(curl -sS -X POST "${TITAN_API_URL}/api/v1/tasks/${TASK_ID}/run" \
      -H "Authorization: Bearer ${TITAN_TOKEN}")
    EXEC_ID=$(echo "$RUN_JSON" | jq -r '.data.id')

    while true; do
      ST=$(titan_get "/executions/${EXEC_ID}" | jq -r '.data.status')
      echo "status=$ST"
      echo "$ST" | grep -qE 'completed|failed|cancelled|completed_with_errors' && break
      sleep 3
    done

    curl -sS "${TITAN_API_URL}/api/v1/executions/${EXEC_ID}/results/export" \
      -H "Authorization: Bearer ${TITAN_TOKEN}" -o results.json
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Uses the same imports as the Go "Build a minimal client" helper (bytes, encoding/json, fmt, io, net/http, os, time).

    base := os.Getenv("TITAN_API_URL")
    tok := os.Getenv("TITAN_TOKEN")

    createBody, _ := json.Marshal(map[string]any{
      "name": "My task", "urls": []string{"https://example.com"},
      "objective": "Extract data",
      "output_schema": map[string]any{"type": "object", "properties": map[string]any{"title": map[string]string{"type": "string"}}},
      "execution_type": "single",
    })
    cr, _ := http.NewRequest("POST", base+"/api/v1/tasks", bytes.NewReader(createBody))
    cr.Header.Set("Authorization", "Bearer "+tok)
    cr.Header.Set("Content-Type", "application/json")
    crResp, _ := http.DefaultClient.Do(cr)
    crBytes, _ := io.ReadAll(crResp.Body)
    crResp.Body.Close()
    var createEnv struct{ Data struct{ ID string `json:"id"` } `json:"data"` }
    json.Unmarshal(crBytes, &createEnv)
    taskID := createEnv.Data.ID

    rr, _ := http.NewRequest("POST", base+"/api/v1/tasks/"+taskID+"/run", nil)
    rr.Header.Set("Authorization", "Bearer "+tok)
    runResp, _ := http.DefaultClient.Do(rr)
    runBytes, _ := io.ReadAll(runResp.Body)
    runResp.Body.Close()
    var runEnv struct {
      Data struct {
        ID     string `json:"id"`
        Status string `json:"status"`
      } `json:"data"`
    }
    json.Unmarshal(runBytes, &runEnv)
    execID, status := runEnv.Data.ID, runEnv.Data.Status

    for status == "queued" || status == "running" || status == "paused" {
      time.Sleep(3 * time.Second)
      pr, _ := http.NewRequest("GET", base+"/api/v1/executions/"+execID, nil)
      pr.Header.Set("Authorization", "Bearer "+tok)
      pResp, _ := http.DefaultClient.Do(pr)
      pBytes, _ := io.ReadAll(pResp.Body)
      pResp.Body.Close()
      json.Unmarshal(pBytes, &runEnv)
      status = runEnv.Data.Status
    }

    er, _ := http.NewRequest("GET", base+"/api/v1/executions/"+execID+"/results/export", nil)
    er.Header.Set("Authorization", "Bearer "+tok)
    eResp, _ := http.DefaultClient.Do(er)
    exportBytes, _ := io.ReadAll(eResp.Body)
    eResp.Body.Close()
    os.WriteFile("results.json", exportBytes, 0o644)
    fmt.Println("done", taskID, execID, status)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const base = process.env.TITAN_API_URL!;
    const token = process.env.TITAN_TOKEN!;
    // Assumes titanRequest / titanJson from "Build a minimal client" (same names).
    const task = await titanRequest<{ id: string }>("POST", "/tasks", {
      name: "My task",
      urls: ["https://example.com"],
      objective: "Extract data",
      output_schema: { type: "object", properties: { title: { type: "string" } } },
      execution_type: "single",
    });

    const runRes = await fetch(`${base}/api/v1/tasks/${task.id}/run`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
    });
    const run = await titanJson<{ id: string; status: string }>(runRes);

    let exec = run;
    while (["queued", "running", "paused"].includes(exec.status)) {
      await new Promise((r) => setTimeout(r, 3000));
      exec = await titanRequest<{ id: string; status: string }>(
        "GET",
        `/executions/${run.id}`,
      );
    }

    const exportRes = await fetch(`${base}/api/v1/executions/${run.id}/results/export`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    if (!exportRes.ok) throw new Error(await exportRes.text());
    const results = JSON.parse(await exportRes.text());
    console.log(results);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Assumes titan_request from "Build a minimal client".

    task = titan_request(
        "POST",
        "/tasks",
        {
            "name": "My task",
            "urls": ["https://example.com"],
            "objective": "Extract data",
            "output_schema": {
                "type": "object",
                "properties": {"title": {"type": "string"}},
            },
            "execution_type": "single",
        },
    )
    task_id = task["id"]

    import json
    import os
    import time
    import urllib.request

    base = os.environ["TITAN_API_URL"]
    tok = os.environ["TITAN_TOKEN"]
    req = urllib.request.Request(
        f"{base}/api/v1/tasks/{task_id}/run",
        headers={"Authorization": f"Bearer {tok}"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        run = json.loads(resp.read().decode())["data"]
    exec_id, status = run["id"], run["status"]

    while status in ("queued", "running", "paused"):
        time.sleep(3)
        req = urllib.request.Request(
            f"{base}/api/v1/executions/{exec_id}",
            headers={"Authorization": f"Bearer {tok}"},
            method="GET",
        )
        with urllib.request.urlopen(req) as resp:
            status = json.loads(resp.read().decode())["data"]["status"]

    req = urllib.request.Request(
        f"{base}/api/v1/executions/{exec_id}/results/export",
        headers={"Authorization": f"Bearer {tok}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp:
        results = json.loads(resp.read().decode())
    print(results)
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::json;

    let base = std::env::var("TITAN_API_URL").expect("TITAN_API_URL");
    let token = std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN");

    let task: serde_json::Value = {
        let body = json!({
            "name": "My task",
            "urls": ["https://example.com"],
            "objective": "Extract data",
            "output_schema": { "type": "object", "properties": { "title": { "type": "string" } } },
            "execution_type": "single"
        });
        let resp = ureq::post(format!("{base}/api/v1/tasks"))
            .set("Authorization", &format!("Bearer {token}"))
            .set("Content-Type", "application/json")
            .send_json(body).expect("http");
        let v: serde_json::Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
        v["data"].clone()
    };
    let task_id = task["id"].as_str().unwrap();

    let run: serde_json::Value = {
        let resp = ureq::post(format!("{base}/api/v1/tasks/{task_id}/run"))
            .set("Authorization", &format!("Bearer {token}"))
            .send_json(json!({})).expect("http");
        let v: serde_json::Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
        v["data"].clone()
    };
    let exec_id = run["id"].as_str().unwrap().to_string();
    let mut status = run["status"].as_str().unwrap().to_string();

    while matches!(status.as_str(), "queued" | "running" | "paused") {
        std::thread::sleep(std::time::Duration::from_secs(3));
        let resp = ureq::get(format!("{base}/api/v1/executions/{exec_id}"))
            .set("Authorization", &format!("Bearer {token}"))
            .call().expect("http");
        let v: serde_json::Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
        status = v["data"]["status"].as_str().unwrap().to_string();
    }

    let resp = ureq::get(format!("{base}/api/v1/executions/{exec_id}/results/export"))
        .set("Authorization", &format!("Bearer {token}"))
        .call().expect("http");
    let results: serde_json::Value = serde_json::from_str(&resp.into_string().expect("body")).expect("json");
    println!("{results}");
    Ok::<(), Box<dyn std::error::Error>>(())
    ```
  </Tab>
</Tabs>

## Best practices

| Practice                                         | Why it matters                          |
| ------------------------------------------------ | --------------------------------------- |
| Keep one reusable task per workflow              | Produces cleaner execution history      |
| Parse the response envelope consistently         | Avoids ad hoc response handling         |
| Poll executions, then switch to result endpoints | Keeps runtime and result logic separate |
| Use API keys for automation                      | Avoids browser-session coupling         |
| Keep output schemas stable                       | Makes downstream consumption simpler    |

## Troubleshooting

| Problem                             | What to check                                      |
| ----------------------------------- | -------------------------------------------------- |
| Task creation returns `400`         | Invalid body or output schema                      |
| Execution never moves past `queued` | Worker availability or scheduler health            |
| Export endpoints fail               | Execution may not be completed yet                 |
| Media does not render in frontend   | Use the correct export mode or media download path |

## Next steps

* [Create and manage tasks](/use-the-platform/create-and-manage-tasks)
* [Monitor and control executions](/use-the-platform/monitor-and-control-executions)
* [Download results and access media](/use-the-platform/download-results-and-access-media)
* [HTTP errors and exceptions](/api-reference/errors) — non-standard error bodies and rate limits
