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
# Start the dedicated async jobs server (port 8005)
praisonai serve jobs --port 8005Base URL: http://127.0.0.1:8005
Combined unified server
# The unified server bundles the jobs API alongside other providers (port 8765)
praisonai serve unified --host 127.0.0.1 --port 8765Base URL: http://127.0.0.1:8765
Authentication
Send the key with either header when PRAISONAI_JOBS_API_KEY is set.
# 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.
stringOptional key to prevent duplicate submissions
stringThe prompt/query for the agent
stringPath 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.
stringInline agents YAML content
stringFramework 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().
objectAdditional configuration
stringURL to POST results when complete
integerTimeout in seconds
stringSession ID for grouping jobs
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).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):
{
"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 statusRetry-After: Suggested seconds before first poll (default: 2)
GET /api/v1/runs
List jobs with optional filters.
stringFilter by status: pending, running, succeeded, failed, cancelled
stringFilter by session ID
integerPage number
integerJobs per page (max 100)
curl "http://127.0.0.1:8765/api/v1/runs?status=running&page=1"Response:
{
"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.
stringJob ID
curl http://127.0.0.1:8765/api/v1/runs/job-abc123Response:
{
"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).
stringJob ID
curl http://127.0.0.1:8765/api/v1/runs/job-abc123/resultResponse (200 OK):
{
"job_id": "job-abc123",
"status": "succeeded",
"result": "Research findings on AI agents..."
}Response (409 Conflict – not complete):
{
"detail": "Job is not complete. Current status: running"
}POST /api/v1/runs/{job_id}/cancel
Cancel a running job.
stringJob ID
curl -X POST http://127.0.0.1:8765/api/v1/runs/job-abc123/cancelResponse:
{
"job_id": "job-abc123",
"status": "cancelled"
}DELETE /api/v1/runs/{job_id}
Delete a completed job.
stringJob ID
curl -X DELETE http://127.0.0.1:8765/api/v1/runs/job-abc123Response: 204 No Content
GET /api/v1/runs/{job_id}/stream
Stream job progress via SSE.
stringJob ID
curl -N http://127.0.0.1:8765/api/v1/runs/job-abc123/streamimport 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())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
| Status | Description |
|---|---|
pending | Job submitted, waiting to start |
running | Job is executing |
succeeded | Job completed successfully |
failed | Job failed with error |
cancelled | Job was cancelled |
Errors
| Status | Description |
|---|---|
| 202 | Job accepted |
| 200 | Success |
| 204 | Deleted |
| 400 | Invalid request (including an agent_file outside PRAISONAI_JOBS_AGENT_ROOT) |
| 401 | Missing or invalid API key |
| 404 | Job not found |
| 409 | Conflict (job not complete or still running) |
| 500 | Server error |
Idempotency
Use Idempotency-Key header to prevent duplicate job submissions:
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).SqliteJobStoreadds anownercolumn 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
- A2U API – Event streaming
- Recipe API – Sync recipe execution