Async Operations

Many GPCN™ operations — creating VMs, attaching volumes, allocating public IPs — are asynchronous. Instead of blocking until complete, the API returns immediately with a job ID that you poll for progress.

How It Works

  1. Submit the operation — the API returns 202 Accepted with a job object
  2. Poll for status — send the job ID to the polling endpoint
  3. Check completion — the job transitions through statuses until it reaches completed or failed

Job Statuses

Status Description
pending Job is queued but hasn't started
in_progress Job is actively being processed
completed Job finished successfully. The resourceId field contains the created/modified resource.
failed Job encountered an error. The error field contains details.

Polling Endpoint

POST /resource/jobs

Poll one or more jobs in a single request (batch polling).

Request Body

{ "jobIds": ["job-uuid-1", "job-uuid-2"] }

1–50 job IDs per request.

Response (200 OK)

{
  "success": true,
  "data": {
    "jobs": [
      {
        "id": "job-uuid-1",
        "status": "completed",
        "progress": 100,
        "resourceType": "vm",
        "resourceId": "550e8400-e29b-41d4-a716-446655440000",
        "error": null,
        "createdAt": "2026-02-21T10:00:00Z",
        "updatedAt": "2026-02-21T10:02:30Z",
        "completedAt": "2026-02-21T10:02:30Z"
      },
      {
        "id": "job-uuid-2",
        "status": "in_progress",
        "progress": 45,
        "resourceType": "volume",
        "resourceId": null,
        "error": null,
        "createdAt": "2026-02-21T10:01:00Z",
        "updatedAt": "2026-02-21T10:02:30Z",
        "completedAt": null
      }
    ]
  }
}

Job Response Fields

Field Type Description
id UUID Job identifier
status string pending, in_progress, completed, or failed
progress integer 0–100 percentage
resourceType string vm, volume, network, gpu
resourceId UUID ID of the created/modified resource (null until completed)
error string Error message if failed (null otherwise)
createdAt ISO 8601 When the job was created
updatedAt ISO 8601 Last status update
completedAt ISO 8601 When the job finished (null if still running)

Polling Pattern

Simple polling loop

# Submit the operation
RESPONSE=$(curl -s -X POST "https://api.gpcn.com/v1/resource/virtual-machines" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: gpcn_your_api_key_here" \
  -d '{"name": "web-server", "datacenterId": "dc-uuid", "imageId": 12, "configurationId": 45}')

JOB_ID=$(echo $RESPONSE | jq -r '.data[0].id')

# Poll until complete
while true; do
  STATUS=$(curl -s -X POST "https://api.gpcn.com/v1/resource/jobs" \
    -H "Content-Type: application/json" \
    -H "X-API-Key: gpcn_your_api_key_here" \
    -d "{\"jobIds\": [\"$JOB_ID\"]}" | jq -r '.data.jobs[0].status')

  echo "Status: $STATUS"

  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
    break
  fi

  sleep 3
done
// Submit the operation
const createResponse = await fetch(
  "https://api.gpcn.com/v1/resource/virtual-machines",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.GPCN_API_KEY!,
    },
    body: JSON.stringify({
      name: "web-server",
      datacenterId: "dc-uuid",
      imageId: 12,
      configurationId: 45,
    }),
  }
);
const { data: jobs } = await createResponse.json();
const jobId = jobs[0].id;

// Poll until complete
async function pollJob(jobId: string): Promise<any> {
  while (true) {
    const pollResponse = await fetch(
      "https://api.gpcn.com/v1/resource/jobs",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": process.env.GPCN_API_KEY!,
        },
        body: JSON.stringify({ jobIds: [jobId] }),
      }
    );
    const { data } = await pollResponse.json();
    const job = data.jobs[0];

    if (job.status === "completed" || job.status === "failed") {
      return job;
    }

    await new Promise((resolve) => setTimeout(resolve, 3000));
  }
}

const result = await pollJob(jobId);
if (result.status === "completed") {
  console.log("Resource created:", result.resourceId);
} else {
  console.error("Job failed:", result.error);
}
import requests, os, time

API = "https://api.gpcn.com/v1"
HEADERS = {"Content-Type": "application/json", "X-API-Key": os.environ["GPCN_API_KEY"]}

# Submit the operation
resp = requests.post(f"{API}/resource/virtual-machines", headers=HEADERS, json={
    "name": "web-server", "datacenterId": "dc-uuid", "imageId": 12, "configurationId": 45
})
job_id = resp.json()["data"][0]["id"]

# Poll until complete
while True:
    poll = requests.post(f"{API}/resource/jobs", headers=HEADERS, json={"jobIds": [job_id]})
    job = poll.json()["data"]["jobs"][0]
    print(f"Status: {job['status']}")
    if job["status"] in ("completed", "failed"):
        break
    time.sleep(3)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

func main() {
	api := "https://api.gpcn.com/v1"
	apiKey := os.Getenv("GPCN_API_KEY")

	// Submit the operation
	body, _ := json.Marshal(map[string]any{
		"name": "web-server", "datacenterId": "dc-uuid",
		"imageId": 12, "configurationId": 45,
	})
	req, _ := http.NewRequest("POST", api+"/resource/virtual-machines", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-API-Key", apiKey)
	resp, _ := http.DefaultClient.Do(req)

	var createResult struct{ Data []struct{ ID string } }
	json.NewDecoder(resp.Body).Decode(&createResult)
	resp.Body.Close()
	jobID := createResult.Data[0].ID

	// Poll until complete
	for {
		pollBody, _ := json.Marshal(map[string]any{"jobIds": []string{jobID}})
		req, _ := http.NewRequest("POST", api+"/resource/jobs", bytes.NewReader(pollBody))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-API-Key", apiKey)
		resp, _ := http.DefaultClient.Do(req)

		var pollResult struct {
			Data struct{ Jobs []struct{ Status string } }
		}
		json.NewDecoder(resp.Body).Decode(&pollResult)
		resp.Body.Close()
		status := pollResult.Data.Jobs[0].Status
		fmt.Println("Status:", status)
		if status == "completed" || status == "failed" {
			break
		}
		time.Sleep(3 * time.Second)
	}
}
using System.Net.Http.Json;

var api = "https://api.gpcn.com/v1";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key",
    Environment.GetEnvironmentVariable("GPCN_API_KEY"));

// Submit the operation
var createResp = await client.PostAsJsonAsync($"{api}/resource/virtual-machines",
    new { name = "web-server", datacenterId = "dc-uuid", imageId = 12, configurationId = 45 });
var createJson = await createResp.Content.ReadFromJsonAsync<JsonElement>();
var jobId = createJson.GetProperty("data")[0].GetProperty("id").GetString();

// Poll until complete
while (true) {
    var pollResp = await client.PostAsJsonAsync($"{api}/resource/jobs",
        new { jobIds = new[] { jobId } });
    var pollJson = await pollResp.Content.ReadFromJsonAsync<JsonElement>();
    var status = pollJson.GetProperty("data").GetProperty("jobs")[0]
        .GetProperty("status").GetString();
    Console.WriteLine($"Status: {status}");
    if (status is "completed" or "failed") break;
    await Task.Delay(3000);
}

Recommendations

  • Poll interval: Start at 2–3 seconds. For long-running operations (VM creation), you can increase to 5–10 seconds after the first few polls.
  • Timeout: Set a reasonable timeout (e.g., 5 minutes for VM creation). If the job hasn't completed, surface an error to the user.
  • Batch polling: If you've submitted multiple jobs (e.g., bulk VM creation), poll all job IDs in a single request instead of one at a time.
  • Progress tracking: Use the progress field (0–100) to display a progress bar in your UI.

Operations That Return Jobs

Resource Operations
Virtual Machines Create, delete, resize, add/remove NIC, allocate/release public IP
GPU VMs Create, delete
Volumes Create, delete, attach, detach, resize
Networks Create

Operations that return 200 OK (start, stop, reset, rename) are synchronous and do not create jobs.

Troubleshooting

Issue Solution
Rate-limited while polling You are polling too frequently. Use a minimum 2–3 second interval between requests, and increase to 5–10 seconds for long-running operations. Batch multiple job IDs into a single /resource/jobs call instead of polling each job separately.
Job stuck in in_progress Set a client-side timeout (e.g., 5 minutes for VM creation, 2 minutes for volume operations). If the job exceeds the timeout, log the job ID and surface an error to the user. GPCN™ jobs that genuinely stall will eventually transition to failed.
Not handling failed status Always check for failed in your polling loop. Read the error field for details — common causes include insufficient quota, invalid configuration, or datacenter capacity limits. Never assume a job will succeed.
Missed job completion If your application crashes or disconnects during polling, persist the job ID and resume polling on restart. Job results remain queryable for at least 24 hours after completion. Use the completedAt field to confirm when the job finished.
Polling returns empty jobs array Verify the job ID is correct and that you are authenticated with the same account that created the job. Job IDs from other tenants are not visible to your API key.
resourceId is null on completed job Some delete operations complete without a resourceId because the resource no longer exists. Check the resourceType field and the operation context to determine expected behavior.

Next Steps