Jobs API
The Jobs API lets you create background jobs, query their status, cancel pending work, and manually retry failures. These endpoints are called by the SDK's IJobClient and can also be used directly via HTTP.
Base URL: https://api.zeridion.com
All endpoints require Bearer token authentication and are subject to rate limits.
POST /v1/jobs
Create a new background job. The job is persisted and will be picked up by a worker for execution.
Request
POST /v1/jobs
Authorization: Bearer <api_key>
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
job_type | string | Yes | Job type identifier, typically the fully qualified class name. Max 500 characters. |
payload | object | Yes | JSON payload passed to the job at execution time. |
queue | string | No | Target queue name. Max 100 characters. Default: "default". |
run_at | string (ISO 8601) | No | Schedule the job for future execution. Omit for immediate. |
max_attempts | integer | No | Maximum execution attempts (1–100). Default: 3. |
timeout_seconds | integer | No | Per-attempt timeout (1–86,400). Default: 1800 (30 minutes). |
idempotency_key | string | No | Unique key for deduplication. Max 200 characters. |
tags | object | No | Key-value string pairs (Dictionary<string, string>) visible in the dashboard. Both keys and values must be strings. |
parent_job_id | string | No | Create as a continuation of this parent job. Max 36 characters. |
Full example
{
"job_type": "email.send",
"payload": {
"to": "user@example.com",
"subject": "Welcome!",
"body": "Thanks for signing up."
},
"queue": "email",
"max_attempts": 5,
"timeout_seconds": 60,
"idempotency_key": "welcome-email-user-42",
"tags": {
"env": "dev",
"priority": "high"
}
}
Minimal example
{
"job_type": "report.generate",
"payload": { "report_id": 1 }
}
Response
201 Created
Returns a Location header with the job URL: Location: /v1/jobs/\{id\}
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"state": "pending",
"job_type": "email.send",
"queue": "email",
"created_at": "2026-03-18T15:30:00Z",
"run_at": null,
"attempt": 0,
"max_attempts": 5
}
| Field | Type | Description |
|---|---|---|
id | string | Unique job identifier |
state | string | Initial state: pending (immediate) or scheduled (has run_at or parent_job_id) |
job_type | string | Echoed job type |
queue | string | Resolved queue name |
created_at | string (ISO 8601) | Timestamp of creation |
run_at | string (ISO 8601) | Scheduled execution time, or null for immediate |
attempt | integer | Current attempt number (starts at 0) |
max_attempts | integer | Maximum attempts allowed |
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | invalid_request | Validation failed (see validation rules) |
| 409 | idempotency_conflict | A job with the same idempotency_key already exists |
| 409 | parent_terminal | Parent job is cancelled or dead_letter |
| 422 | parent_not_found | parent_job_id does not reference an existing job |
GET /v1/jobs/{job_id}
Retrieve full details for a single job, including payload, timing, error information, and tags.
Request
GET /v1/jobs/{job_id}
Authorization: Bearer <api_key>
Response
200 OK
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"state": "succeeded",
"job_type": "email.send",
"queue": "email",
"payload": { "to": "user@example.com", "subject": "Welcome!" },
"created_at": "2026-03-18T15:30:00Z",
"started_at": "2026-03-18T15:30:01Z",
"completed_at": "2026-03-18T15:30:02Z",
"attempt": 1,
"max_attempts": 5,
"progress": 1.0,
"duration_ms": 1042,
"error": null,
"tags": { "env": "dev", "priority": "high" }
}
| Field | Type | Description |
|---|---|---|
id | string | Unique job identifier |
state | string | Current job state: pending, scheduled, processing, succeeded, failed, cancelled, dead_letter |
job_type | string | Job type identifier |
queue | string | Queue the job belongs to |
payload | object | The job's JSON payload |
created_at | string (ISO 8601) | When the job was created |
started_at | string (ISO 8601) | When execution began, or null |
completed_at | string (ISO 8601) | When execution finished, or null |
attempt | integer | Current attempt number |
max_attempts | integer | Maximum attempts allowed |
progress | number | Progress value (0.0–1.0) reported by the job, or null |
duration_ms | integer | Execution duration in milliseconds, or null |
error | object | Error details if the job failed (see below), or null |
tags | object | Key-value string pairs (Dictionary<string, string>), or null |
Error detail object
When a job fails, the error field contains:
| Field | Type | Description |
|---|---|---|
type | string | Exception type name |
message | string | Error message |
stack_trace | string | Stack trace from the failed attempt |
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | job_not_found | Job does not exist or belongs to a different project |
GET /v1/jobs
List jobs with optional filtering and cursor-based pagination.
Request
GET /v1/jobs?state=failed&queue=email&limit=20
Authorization: Bearer <api_key>
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
state | string | all | Filter by state: pending, scheduled, processing, succeeded, failed, cancelled, dead_letter |
queue | string | all | Filter by queue name |
job_type | string | all | Filter by job type |
created_after | string (ISO 8601) | — | Only return jobs created after this timestamp |
created_before | string (ISO 8601) | — | Only return jobs created before this timestamp |
limit | integer | 50 | Number of results per page (1–100) |
cursor | string | — | Cursor from a previous response's next_cursor for the next page |
Response
200 OK
{
"data": [
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"state": "failed",
"job_type": "email.send",
"queue": "email",
"payload": { "to": "user@example.com" },
"created_at": "2026-03-18T15:30:00Z",
"started_at": "2026-03-18T15:30:01Z",
"completed_at": "2026-03-18T15:30:02Z",
"attempt": 3,
"max_attempts": 3,
"progress": null,
"duration_ms": 503,
"error": {
"type": "System.Net.Http.HttpRequestException",
"message": "Connection refused",
"stack_trace": " at System.Net.Http..."
},
"tags": null
}
],
"has_more": true,
"next_cursor": "eyJpZCI6ImpvYl8wMUhZWDNLN004TjlQMlE0UjVTNlQ3VThWOSJ9"
}
| Field | Type | Description |
|---|---|---|
data | array | Array of job detail objects |
has_more | boolean | true if more results exist beyond this page |
next_cursor | string | Pass as the cursor query parameter to fetch the next page. null when has_more is false. |
Sort order
Results are sorted by created_at descending (newest first), with ties broken by id descending.
Pagination
The API uses cursor-based pagination to provide consistent results under concurrent writes. To paginate through all results:
- Make the initial request without a
cursor - If
has_moreistrue, use thenext_cursorvalue as thecursorparameter in the next request - Repeat until
has_moreisfalse
# Page 1
curl "https://api.zeridion.com/v1/jobs?state=failed&limit=20" \
-H "Authorization: Bearer $API_KEY"
# Page 2 (using next_cursor from page 1)
curl "https://api.zeridion.com/v1/jobs?state=failed&limit=20&cursor=eyJ..." \
-H "Authorization: Bearer $API_KEY"
POST /v1/jobs/{job_id}/cancel
Cancel a job. Only jobs in pending or scheduled state can be cancelled.
Request
POST /v1/jobs/{job_id}/cancel
Authorization: Bearer <api_key>
No request body is required.
Response
200 OK
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"state": "cancelled"
}
Behavior
- Pending / Scheduled jobs are cancelled immediately.
- Processing, succeeded, failed, cancelled, and dead-letter jobs cannot be cancelled — the endpoint returns 409 Conflict.
- Parent cancellation cascades: cancelling a parent job also cancels any child continuation jobs that are still in
scheduledstate.
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | job_not_found | Job does not exist |
| 409 | invalid_state | Job is not in pending or scheduled state |
POST /v1/jobs/{job_id}/retry
Manually retry a failed or dead-lettered job. Resets the job to pending state so it can be picked up by a worker again.
Request
POST /v1/jobs/{job_id}/retry
Authorization: Bearer <api_key>
No request body is required.
Response
200 OK
{
"id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9",
"state": "pending",
"attempt": 3
}
| Field | Type | Description |
|---|---|---|
id | string | Job identifier |
state | string | Always pending after a successful retry |
attempt | integer | Current attempt number |
Behavior
- Only jobs in
failedordead_letterstate can be retried. - If the job has exhausted all attempts (
attempt >= max_attempts), themax_attemptsis automatically bumped toattempt + 1to allow the retry. - The job's error details, timing fields, and worker assignment are cleared.
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | job_not_found | Job does not exist |
| 409 | invalid_state | Job is not in failed or dead_letter state |
Job Continuations
Continuations let you chain jobs together in a parent-child relationship. A child job waits for its parent to succeed before it runs.
Creating a continuation
Include parent_job_id when creating a job via POST /v1/jobs:
{
"job_type": "notification.send",
"payload": { "channel": "slack", "message": "Data sync complete" },
"queue": "default",
"parent_job_id": "job_01HYX3K7M8N9P2Q4R5S6T7U8V9"
}
State behavior
The child's initial state depends on the parent's current state:
| Parent state | Child initial state | Behavior |
|---|---|---|
pending / scheduled / processing | scheduled | Child waits for parent to complete |
succeeded | pending | Child runs immediately (parent already done) |
cancelled / dead_letter | — | Creation rejected with 409 parent_terminal |
Activation and cascading
- Parent succeeds: all child jobs in
scheduledstate are activated (moved topending). The parent's ack response includeschildren_activatedwith the count. - Parent dead-letters: all child jobs in
scheduledstate are cancelled. - Parent is cancelled: all child jobs in
scheduledstate are cancelled.
Errors
| Status | Code | Condition |
|---|---|---|
| 409 | parent_terminal | Parent is cancelled or dead_letter |
| 422 | parent_not_found | The parent_job_id does not reference an existing job |