/api/transactions/task

GET

Query the status, progress, and results of asynchronous operations using task IDs.

Description

The `/api/transactions/task` endpoint (also accessible as `/api/task`) retrieves the real-time status and results of asynchronous operations. When you initiate operations like import, authenticate, or fix, they return immediately with a task ID. Use this endpoint to poll for completion and retrieve final results.

💡 Typical Usage Pattern
  1. Start Operation: Call an async endpoint (e.g., /api/transactions/import)
  2. Receive Task ID: The response includes a unique task_id
  3. Poll Status: Call /api/task?task_id=... repeatedly (every 1-2 seconds)
  4. Check Progress: Monitor the progress field (0-100)
  5. Get Results: When status="success" and progress=100, read final results from data field
📂 Task Storage

Task status files are stored in [data_path]/Tasks/[task_id].json and updated in real-time by background threads. This endpoint reads and returns the raw JSON file contents (max 64KB file size).

Parameters

Parameter Type Required Description
task_id string Yes Task identifier in format MMM-DD-YY_HH-MM-SS-AM/PM (e.g., "Jan-31-25_03-45-12-PM"). Returned by async operation endpoints.

Response

Returns the raw task status JSON file. The structure varies by operation type, but all tasks share common fields.

Common Response Properties

status string
Task status: "pending", "running", "success", or "error".
task_id string
The task identifier (matches the requested task_id).
progress integer
Completion percentage (0-100). Reaches 100 when task completes.
message string
Human-readable status message describing current state or final result.
data object
Operation-specific data. Structure varies by operation type (see examples below).

Example Response (Import Operation - Completed)

{
  "status": "success",
  "task_id": "Oct-26-25_09-15-16-AM",
  "progress": 100,
  "message": "Import, authentication, and grading complete",
  "data": {
    "total_processed": 2,
    "already_exists": 0,
    "pown_results": {
      "bank": 2,
      "fracked": 0,
      "counterfeit": 0,
      "limbo": 0,
      "errors": 0,
      "suspect": 0
    },
    "receipt_id": "Oct-26-25_09-15-16-AM"
  }
}

Example Response (Import Operation - In Progress)

{
  "status": "running",
  "task_id": "Nov-01-25_05-49-16-PM",
  "progress": 45,
  "message": "Authenticating coins with RAIDA servers...",
  "data": {
    "total_processed": 0,
    "already_exists": 0,
    "pown_results": {
      "bank": 0,
      "fracked": 0,
      "counterfeit": 0,
      "limbo": 0,
      "errors": 0,
      "suspect": 0
    }
  }
}

Example Response (Authenticate Operation - Completed)

{
  "status": "success",
  "task_id": "Jan-31-25_03-45-12-PM",
  "progress": 100,
  "message": "Authentication and grading complete",
  "data": {
    "operation": "authenticate",
    "coins_processed": 12,
    "coins_authenticated": 10,
    "coins_fracked": 2,
    "coins_counterfeit": 0,
    "raida_responses": {
      "average_time_ms": 1234,
      "timeouts": 0,
      "errors": 1
    },
    "receipt_id": "Jan-31-25_03-45-12-PM"
  }
}

Example Response (Fix Operation - Completed)

{
  "status": "success",
  "task_id": "Jan-31-25_04-12-30-PM",
  "progress": 100,
  "message": "Healing complete - 3 coins fully recovered, 1 remains fracked",
  "data": {
    "operation": "fix",
    "rounds_completed": 3,
    "coins_healed": 3,
    "coins_still_fracked": 1,
    "coins_lost": 0,
    "receipt_id": "Jan-31-25_04-12-30-PM"
  }
}

Error Responses

Missing Parameter (400)

{
  "error": "Missing required parameter: task_id"
}

Task Not Found (404)

{
  "error": "Task ID not found"
}

Invalid File Size (500)

{
  "error": "Invalid task status file size (must be ≤64KB)"
}

Task ID Format

Task IDs are human-readable timestamps generated by the task manager when operations start.

🕐 Task ID Structure

Format: MMM-DD-YY_HH-MM-SS-AM/PM

Examples:

  • Jan-31-25_03-45-12-PM - January 31, 2025 at 3:45:12 PM
  • Oct-26-25_09-15-16-AM - October 26, 2025 at 9:15:16 AM
  • Nov-01-25_05-49-16-PM - November 1, 2025 at 5:49:16 PM

Generated by: task_manager_generate_id() in C backend

Examples

JavaScript (fetch)

const API_HOST = 'http://localhost:8080';

// Simple polling function
async function getTaskStatus(taskId) {
    try {
        const response = await fetch(`${API_HOST}/api/task?task_id=${taskId}`);

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const task = await response.json();
        return task;
    } catch (error) {
        console.error('Error fetching task status:', error);
        throw error;
    }
}

// Poll until completion
async function waitForTaskCompletion(taskId, pollIntervalMs = 1000) {
    console.log(`Waiting for task ${taskId} to complete...`);

    while (true) {
        const task = await getTaskStatus(taskId);

        console.log(`Progress: ${task.progress}% - ${task.message}`);

        if (task.status === 'success' && task.progress === 100) {
            console.log('Task completed successfully!');
            console.log('Final results:', task.data);
            return task;
        } else if (task.status === 'error') {
            console.error('Task failed:', task.message);
            throw new Error(task.message);
        }

        // Wait before next poll
        await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
    }
}

// Example usage
async function importAndWait(files, memo) {
    // Start import
    const filesParam = files.join(',');
    const importUrl = `${API_HOST}/api/transactions/import?` +
                      `files=${encodeURIComponent(filesParam)}&` +
                      `memo=${encodeURIComponent(memo)}`;

    const importResp = await fetch(importUrl);
    const importResult = await importResp.json();

    if (importResult.status !== 'success') {
        throw new Error('Import failed to start');
    }

    console.log(`Import started with task ID: ${importResult.task_id}`);

    // Wait for completion
    const finalTask = await waitForTaskCompletion(importResult.task_id);

    console.log(`Imported ${finalTask.data.total_processed} files`);
    console.log(`Bank: ${finalTask.data.pown_results.bank} coins`);
    console.log(`Fracked: ${finalTask.data.pown_results.fracked} coins`);
}

// Run example
importAndWait([
    'C:\\Users\\User\\Downloads\\coins.stack'
], 'Payment received');

cURL

# Query task status (full endpoint)
curl -X GET "http://localhost:8080/api/transactions/task?task_id=Jan-31-25_03-45-12-PM"

# Query task status (short endpoint)
curl -X GET "http://localhost:8080/api/task?task_id=Oct-26-25_09-15-16-AM"

# Poll in a loop (Bash)
TASK_ID="Nov-01-25_05-49-16-PM"
while true; do
    RESPONSE=$(curl -s "http://localhost:8080/api/task?task_id=$TASK_ID")
    echo "$RESPONSE" | jq .

    STATUS=$(echo "$RESPONSE" | jq -r .status)
    PROGRESS=$(echo "$RESPONSE" | jq -r .progress)

    if [ "$STATUS" = "success" ] && [ "$PROGRESS" = "100" ]; then
        echo "Task completed!"
        break
    elif [ "$STATUS" = "error" ]; then
        echo "Task failed!"
        break
    fi

    sleep 1
done

Go

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

const ApiHost = "http://localhost:8080"

type TaskStatus struct {
    Status   string                 `json:"status"`
    TaskID   string                 `json:"task_id"`
    Progress int                    `json:"progress"`
    Message  string                 `json:"message"`
    Data     map[string]interface{} `json:"data"`
}

type POWNResults struct {
    Bank         int `json:"bank"`
    Fracked      int `json:"fracked"`
    Counterfeit  int `json:"counterfeit"`
    Limbo        int `json:"limbo"`
    Errors       int `json:"errors"`
    Suspect      int `json:"suspect"`
}

func getTaskStatus(taskID string) (*TaskStatus, error) {
    url := fmt.Sprintf("%s/api/task?task_id=%s", ApiHost, taskID)

    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        body, _ := ioutil.ReadAll(resp.Body)
        return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    var task TaskStatus
    if err := json.Unmarshal(body, &task); err != nil {
        return nil, err
    }

    return &task, nil
}

func waitForCompletion(taskID string, pollInterval time.Duration) (*TaskStatus, error) {
    fmt.Printf("Waiting for task %s to complete...\n", taskID)

    for {
        task, err := getTaskStatus(taskID)
        if err != nil {
            return nil, err
        }

        fmt.Printf("Progress: %d%% - %s\n", task.Progress, task.Message)

        if task.Status == "success" && task.Progress == 100 {
            fmt.Println("Task completed successfully!")
            return task, nil
        } else if task.Status == "error" {
            return nil, fmt.Errorf("task failed: %s", task.Message)
        }

        time.Sleep(pollInterval)
    }
}

func main() {
    // Example: Check specific task
    taskID := "Oct-26-25_09-15-16-AM"

    task, err := getTaskStatus(taskID)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Task Status: %s\n", task.Status)
    fmt.Printf("Progress: %d%%\n", task.Progress)
    fmt.Printf("Message: %s\n", task.Message)

    // Pretty print data
    dataJSON, _ := json.MarshalIndent(task.Data, "", "  ")
    fmt.Printf("Data:\n%s\n", string(dataJSON))

    // Example: Wait for completion
    // finalTask, err := waitForCompletion(taskID, 1*time.Second)
    // if err != nil {
    //     fmt.Printf("Error: %v\n", err)
    // }
}

Implementation Details

🔧 Backend Implementation

The endpoint is fully implemented as a pass-through file reader:

  1. Validates task_id parameter exists
  2. Constructs file path: [data_path]/Tasks/[task_id].json
  3. Checks if file exists (404 if not found)
  4. Reads JSON file contents (max 64KB)
  5. Returns raw JSON content with Content-Type: application/json

Source: api_handlers.c lines 598-664

⚙️ Task File Lifecycle
  • Created: When async operation starts (by background thread)
  • Updated: Continuously during operation (progress, status, data)
  • Finalized: When operation completes or errors
  • Persisted: Files remain in Tasks folder for historical reference

Related Endpoints

/api/transactions/import

Import CloudCoin files from absolute paths. Returns a task_id for progress tracking.

/api/coins/authenticate

Authenticate coins with RAIDA servers. Async operation returns task_id.

/api/wallet/receipt

View detailed transaction receipts. Tasks include a receipt_id field linking to full receipts.

/api/wallet/transactions

List all transaction history. Each transaction has an associated task_id.