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

# Asynchronous operations and task polling

Write operations in the Gcore Cloud API are asynchronous — the API returns a task ID immediately, while the actual operation runs in the background. The created resource ID is only available after polling the task to completion.

This applies to every operation that changes infrastructure state: creating instances, volumes, networks, load balancers, Kubernetes clusters, and more.

1. Make the request. A write call returns a task ID immediately:

```json theme={null}
{
  "tasks": ["d4a4b10e-5d22-4c23-8e09-f7a3e1c1a999"]
}
```

2. Poll the task. Call `GET /cloud/v1/tasks/{task_id}` repeatedly until the `state` field is no longer `RUNNING`:

```bash theme={null}
curl "https://api.gcore.com/cloud/v1/tasks/{TASK_ID}" \
  -H "Authorization: APIKey $GCORE_API_KEY"
```

3. Extract the result. When `state` is `FINISHED`, find the created resource ID in `created_resources`.

## Task states

Tasks move through four states from creation to completion:

| State      | Meaning                                |
| ---------- | -------------------------------------- |
| `NEW`      | Task queued, not yet started           |
| `RUNNING`  | Operation in progress                  |
| `FINISHED` | Operation completed successfully       |
| `ERROR`    | Operation failed — check `error` field |

## Task response

A finished `create_vm` task response shows the complete structure, including the `created_resources` field where the new resource IDs are found:

```json theme={null}
{
  "id": "d4a4b10e-5d22-4c23-8e09-f7a3e1c1a999",
  "task_type": "create_vm",
  "state": "FINISHED",
  "error": null,
  "created_on": "2026-05-23T05:07:31",
  "finished_on": "2026-05-23T05:07:50",
  "created_resources": {
    "instances": ["169942e0-9b53-42df-95ef-1a8b6525c2bd"],  // → resource ID
    "volumes":   ["726ecfcc-7fd0-4e30-a86e-7892524aa483"],
    "floatingips": [],
    "networks": [],
    ...
  }
}
```

The resource ID is in the `created_resources` field under the resource type key. Only the types that were actually created contain non-empty arrays — all others are empty lists.

## Polling intervals

Polling too frequently wastes API quota; polling too rarely delays pipelines. These intervals balance the two:

| Operation                 | Typical duration | Poll every |
| ------------------------- | ---------------- | ---------- |
| Create VM                 | 15–30 seconds    | 5 seconds  |
| Create volume             | 5–15 seconds     | 5 seconds  |
| Create network / subnet   | 5–10 seconds     | 5 seconds  |
| Create load balancer      | 30–90 seconds    | 10 seconds |
| Delete VM                 | 15–70 seconds    | 5 seconds  |
| Delete Bare Metal server  | 60–120 seconds   | 30 seconds |
| Create Kubernetes cluster | 5–15 minutes     | 30 seconds |

## Poll the task

<Tabs>
  <Tab title="curl (shell loop)">
    ```bash theme={null}
    TASK_ID="{TASK_ID}"

    while true; do
      STATE=$(curl -s "https://api.gcore.com/cloud/v1/tasks/$TASK_ID" \
        -H "Authorization: APIKey $GCORE_API_KEY" \
        | grep -o '"state":"[^"]*"' | grep -o '[A-Z]*$' | tr -d '"')
      echo "State: $STATE"
      if [ "$STATE" = "FINISHED" ]; then break; fi
      if [ "$STATE" = "ERROR" ]; then echo "Task failed"; exit 1; fi
      sleep 5
    done
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    import os, time
    from gcore import Gcore

    client = Gcore(api_key=os.environ["GCORE_API_KEY"])

    # Built-in blocking poll — no manual loop needed
    task = client.cloud.tasks.poll(
        "{TASK_ID}",
        polling_interval_seconds=5,
        polling_timeout_seconds=300,
    )
    instance_id = task.created_resources.instances[0]
    ```
  </Tab>

  <Tab title="Go SDK">
    ```go theme={null}
    import "time"

    taskID := "{TASK_ID}"
    for {
        task, err := client.Cloud.Tasks.Get(context.TODO(), taskID)
        if err != nil { panic(err) }
        if task.State == "FINISHED" {
            instanceID := task.CreatedResources.Instances[0]
            fmt.Println("Instance:", instanceID)
            break
        }
        if task.State == "ERROR" {
            panic("Task failed: " + *task.Error)
        }
        time.Sleep(5 * time.Second)
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    async function pollTask(taskId, intervalMs = 5000) {
      const headers = { Authorization: `APIKey ${process.env.GCORE_API_KEY}` };
      while (true) {
        const res = await fetch(
          `https://api.gcore.com/cloud/v1/tasks/${taskId}`,
          { headers }
        );
        const task = await res.json();
        if (task.state === "FINISHED") return task.created_resources;
        if (task.state === "ERROR") throw new Error(task.error);
        await new Promise(r => setTimeout(r, intervalMs));
      }
    }

    const resources = await pollTask("{TASK_ID}");
    console.log("Instance:", resources.instances[0]);
    ```
  </Tab>
</Tabs>

## Error tasks

When `state` is `ERROR`, the `error` field contains the failure reason:

```json theme={null}
{
  "state": "ERROR",
  "error": "Quota limit for volume_size exceeded by 20",
  "created_resources": null
}
```

On error, no resources from that task are created — there is nothing to clean up from the failed task itself.

## List recent tasks

To see all tasks in a project and region, call:

```bash theme={null}
curl "https://api.gcore.com/cloud/v1/tasks?limit=20&state=RUNNING" \
  -H "Authorization: APIKey $GCORE_API_KEY"
```

Filter by `state` (`NEW`, `RUNNING`, `FINISHED`, `ERROR`) and `task_type` (e.g., `create_vm`, `delete_vm`, `create_volume`).

<Info>
  Tasks are associated with a specific project and region, whereas the task endpoint `GET /cloud/v1/tasks/{task_id}` does not require project\_id or region\_id in the path — a task ID is globally unique.
</Info>
