Jobs API

Async job management API for long-running agent tasks

Jobs API

The Jobs API provides asynchronous job management for long-running agent tasks with progress tracking and streaming.

Overview

Jobs API enables:

  • Async job submission with immediate response
  • Job status polling
  • Progress streaming via SSE
  • Job cancellation and cleanup
  • Idempotency for safe retries

When to Use

  • Long-running tasks: Tasks that take more than a few seconds
  • Background processing: Fire-and-forget with status polling
  • Progress tracking: Real-time progress updates
  • Reliable execution: Idempotent submissions

Base URL + Playground

Dedicated jobs server

bash
# Start the dedicated async jobs server (port 8005)
praisonai serve jobs --port 8005

Base URL: http://127.0.0.1:8005

Combined unified server

bash
# The unified server bundles the jobs API alongside other providers (port 8765)
praisonai serve unified --host 127.0.0.1 --port 8765

Base URL: http://127.0.0.1:8765

Authentication

Send the key with either header when PRAISONAI_JOBS_API_KEY is set.

bash
# Authorization header
curl -H "Authorization: Bearer $JOBS_API_KEY" http://127.0.0.1:8765/api/v1/runs

# X-API-Key header
curl -H "X-API-Key: $JOBS_API_KEY" http://127.0.0.1:8765/api/v1/runs

/health is the only endpoint reachable without a key. Enable the key with PRAISONAI_JOBS_API_KEY — see Async Jobs Deployment.

Endpoints

POST /api/v1/runs

Submit a new job for async execution.

ParamField string

Optional key to prevent duplicate submissions

ParamField string

The prompt/query for the agent

ParamField string

Path to agents YAML file. Must resolve under PRAISONAI_JOBS_AGENT_ROOT (default: the server’s working directory); absolute paths or .. escapes outside it return 400. See Jobs agent_file Root.

ParamField string

Inline agents YAML content

ParamField string

Framework to use: praisonai, crewai, autogen. Omit to use the registry-selected default (praisonai on a standard install; a tenant/custom registry’s pick_default() when one is configured). See Registry Dependency Injection and resolve_or_default().

ParamField object

Additional configuration

ParamField string

URL to POST results when complete

ParamField integer

Timeout in seconds

ParamField string

Session ID for grouping jobs

curl
curl -X POST http://127.0.0.1:8765/api/v1/runs \
  -H "Authorization: Bearer $JOBS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-key-123" \
  -d '{
    "prompt": "Research AI agents",
    "agent_file": "agents.yaml",
    "framework": "praisonai"
  }'
# Omitting "framework" resolves to the registry-selected default (praisonai on a standard install).
Python
import requests

# The request body below omits "framework", so it resolves via the
# registry-selected default rather than a hardcoded "praisonai".
response = requests.post(
    "http://127.0.0.1:8765/api/v1/runs",
    headers={
        "Authorization": f"Bearer {JOBS_API_KEY}",
        "Idempotency-Key": "unique-key-123",
    },
    json={
        "prompt": "Research AI agents",
        "agent_file": "agents.yaml"
    }
)
job = response.json()
print(f"Job ID: {job['job_id']}")
print(f"Poll URL: {job['poll_url']}")

Response (202 Accepted):

json
{
  "job_id": "job-abc123",
  "status": "pending",
  "created_at": "2024-01-15T10:30:00Z",
  "poll_url": "http://127.0.0.1:8765/api/v1/runs/job-abc123",
  "stream_url": "http://127.0.0.1:8765/api/v1/runs/job-abc123/stream"
}

Headers:

  • Location: URL to poll for status
  • Retry-After: Suggested seconds before first poll (default: 2)

GET /api/v1/runs

List jobs with optional filters.

ParamField string

Filter by status: pending, running, succeeded, failed, cancelled

ParamField string

Filter by session ID

ParamField integer

Page number

ParamField integer

Jobs per page (max 100)

bash
curl "http://127.0.0.1:8765/api/v1/runs?status=running&page=1"

Response:

json
{
  "jobs": [
    {
      "job_id": "job-abc123",
      "status": "running",
      "progress_percentage": 45,
      "progress_step": "Analyzing data...",
      "created_at": "2024-01-15T10:30:00Z",
      "started_at": "2024-01-15T10:30:02Z"
    }
  ],
  "total": 15,
  "page": 1,
  "page_size": 20
}

GET /api/v1/runs/{job_id}

Get job status.

job_id string

Job ID

bash
curl http://127.0.0.1:8765/api/v1/runs/job-abc123

Response:

json
{
  "job_id": "job-abc123",
  "status": "running",
  "progress_percentage": 75,
  "progress_step": "Generating report...",
  "created_at": "2024-01-15T10:30:00Z",
  "started_at": "2024-01-15T10:30:02Z"
}

GET /api/v1/runs/{job_id}/result

Get job result (only for completed jobs).

job_id string

Job ID

bash
curl http://127.0.0.1:8765/api/v1/runs/job-abc123/result

Response (200 OK):

json
{
  "job_id": "job-abc123",
  "status": "succeeded",
  "result": "Research findings on AI agents..."
}

Response (409 Conflict – not complete):

json
{
  "detail": "Job is not complete. Current status: running"
}

POST /api/v1/runs/{job_id}/cancel

Cancel a running job.

job_id string

Job ID

bash
curl -X POST http://127.0.0.1:8765/api/v1/runs/job-abc123/cancel

Response:

json
{
  "job_id": "job-abc123",
  "status": "cancelled"
}

DELETE /api/v1/runs/{job_id}

Delete a completed job.

job_id string

Job ID

bash
curl -X DELETE http://127.0.0.1:8765/api/v1/runs/job-abc123

Response: 204 No Content

GET /api/v1/runs/{job_id}/stream

Stream job progress via SSE.

job_id string

Job ID

curl
curl -N http://127.0.0.1:8765/api/v1/runs/job-abc123/stream
Python
import requests

response = requests.get(
    "http://127.0.0.1:8765/api/v1/runs/job-abc123/stream",
    stream=True
)
for line in response.iter_lines():
    if line:
        print(line.decode())
JavaScript
const eventSource = new EventSource(
  "http://127.0.0.1:8765/api/v1/runs/job-abc123/stream"
);

eventSource.addEventListener("progress", (event) => {
  const data = JSON.parse(event.data);
  console.log(`Progress: ${data.percentage}% - ${data.step}`);
});

eventSource.addEventListener("result", (event) => {
  const data = JSON.parse(event.data);
  console.log("Result:", data.result);
  eventSource.close();
});

SSE Events:

event: status
data: {"status": "running", "job_id": "job-abc123"}

event: progress
data: {"percentage": 25, "step": "Researching..."}

event: progress
data: {"percentage": 50, "step": "Analyzing..."}

event: progress
data: {"percentage": 100, "step": "Complete"}

event: result
data: {"result": "Research findings..."}

Job Status Values

StatusDescription
pendingJob submitted, waiting to start
runningJob is executing
succeededJob completed successfully
failedJob failed with error
cancelledJob was cancelled

Errors

StatusDescription
202Job accepted
200Success
204Deleted
400Invalid request (including an agent_file outside PRAISONAI_JOBS_AGENT_ROOT)
401Missing or invalid API key
404Job not found
409Conflict (job not complete or still running)
500Server error

Idempotency

Use Idempotency-Key header to prevent duplicate job submissions:

bash
curl -X POST http://127.0.0.1:8765/api/v1/runs \
  -H "Idempotency-Key: my-unique-key" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Research AI"}'

If the same key is used again, the existing job is returned instead of creating a new one.

Notes

  • Jobs are stored in memory by default
  • Configure persistent storage for production (PRAISONAI_JOBS_DB_PATH). SqliteJobStore adds an owner column to existing databases automatically on first open — no manual migration — so restarts only fail jobs whose writer process is gone
  • Webhook notifications are optional
  • SSE streaming includes heartbeats every 5 seconds

Related