Skip to content

HTTP API Reference

This is the complete HTTP API reference for OJS. All endpoints are served under the base path /ojs/v1 unless noted otherwise.

Every request should include:

HeaderValueRequired
Content-Typeapplication/openjobspec+jsonYes (for POST)
Acceptapplication/openjobspec+jsonRecommended
OJS-Version1.0.0-rc.1Optional

Every response includes:

HeaderDescription
OJS-VersionSpec version used to process the request
Content-Typeapplication/openjobspec+json
X-Request-IdUnique request identifier for tracing

GET /ojs/v1/health

Returns the health status of the OJS server and its backend.

Response (200 OK):

{
"status": "ok",
"version": "1.0.0-rc.1",
"uptime_seconds": 86400,
"backend": {
"type": "redis",
"status": "connected",
"latency_ms": 2
}
}

Response (503 Service Unavailable): Returned when the backend is unreachable.

GET /ojs/manifest

Returns the server’s conformance level and supported features. This endpoint is at /ojs/manifest (no version prefix) so clients can discover capabilities before making versioned requests.

Response (200 OK):

{
"specversion": "1.0.0-rc.1",
"implementation": "ojs-backend-redis",
"implementation_version": "1.0.0",
"conformance_level": 4,
"features": {
"batch_enqueue": true,
"dead_letter": true,
"cron": true,
"workflows": true,
"unique_jobs": true,
"priority": true,
"schemas": true
}
}

POST /ojs/v1/jobs

Enqueues a single job for processing.

Request body:

FieldTypeRequiredDescription
typestringYesDot-namespaced job type (e.g., email.send)
argsarrayYesPositional arguments, JSON-native types only
metaobjectNoExtensible key-value metadata
schemastringNoSchema URI for argument validation
optionsobjectNoEnqueue options (see below)

Options fields:

FieldTypeDefaultDescription
queuestring"default"Target queue name
priorityinteger0Higher values = higher priority
timeout_msinteger30000Max execution time in milliseconds
delay_untilstringnullISO 8601 timestamp for delayed execution
expires_atstringnullISO 8601 expiration deadline
retryobject(default)Retry policy override
uniqueobjectnullDeduplication policy
tagsstring[][]Tags for filtering
visibility_timeout_msinteger30000Reservation period in ms

Example:

Terminal window
curl -X POST http://localhost:8080/ojs/v1/jobs \
-H "Content-Type: application/openjobspec+json" \
-d '{
"type": "email.send",
"args": ["user@example.com", "welcome"],
"options": {
"queue": "email",
"retry": {
"max_attempts": 5
}
}
}'

Response (201 Created):

{
"job": {
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"type": "email.send",
"state": "available",
"args": ["user@example.com", "welcome"],
"queue": "email",
"priority": 0,
"attempt": 0,
"max_attempts": 5,
"created_at": "2026-02-12T10:30:00.000Z",
"enqueued_at": "2026-02-12T10:30:00.123Z"
}
}

The response includes a Location header pointing to the job resource.

GET /ojs/v1/jobs/:id

Returns the complete job envelope including current state and all timestamps.

Response (200 OK):

{
"job": {
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"type": "email.send",
"state": "completed",
"args": ["user@example.com", "welcome"],
"queue": "email",
"attempt": 1,
"created_at": "2026-02-12T10:30:00.000Z",
"enqueued_at": "2026-02-12T10:30:00.123Z",
"started_at": "2026-02-12T10:30:01.456Z",
"completed_at": "2026-02-12T10:30:02.789Z",
"result": { "delivered": true }
}
}

Response (404 Not Found): Job does not exist.

DELETE /ojs/v1/jobs/:id

Cancels a job in a non-terminal state. Idempotent for already-cancelled jobs.

Response (200 OK):

{
"job": {
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"state": "cancelled"
}
}

Response (409 Conflict): Job is already in a terminal state (completed or discarded).

POST /ojs/v1/jobs/batch

Enqueues multiple jobs atomically. Either all jobs are enqueued or none.

Request body:

{
"jobs": [
{
"type": "email.send",
"args": ["alice@example.com", "welcome"]
},
{
"type": "email.send",
"args": ["bob@example.com", "welcome"]
}
]
}

Response (201 Created):

{
"jobs": [
{
"id": "019414d4-8b2e-7c3a-b5d1-aaa000000001",
"state": "available",
"enqueued_at": "2026-02-12T10:30:00.123Z"
},
{
"id": "019414d4-8b2e-7c3a-b5d1-aaa000000002",
"state": "available",
"enqueued_at": "2026-02-12T10:30:00.124Z"
}
],
"count": 2
}

POST /ojs/v1/workers/fetch

Claims one or more jobs for processing.

Request body:

FieldTypeRequiredDescription
queuesstring[]YesQueues to poll, in priority order
countintegerNoNumber of jobs to fetch (default: 1)
worker_idstringNoUnique worker identifier
visibility_timeout_msintegerNoOverride visibility timeout

Example:

Terminal window
curl -X POST http://localhost:8080/ojs/v1/workers/fetch \
-H "Content-Type: application/openjobspec+json" \
-d '{
"queues": ["critical", "default"],
"count": 5,
"worker_id": "worker-abc123"
}'

Response (200 OK):

{
"jobs": [
{
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"type": "email.send",
"state": "active",
"args": ["user@example.com", "welcome"],
"queue": "default",
"attempt": 1,
"started_at": "2026-02-12T10:30:01.000Z"
}
]
}

An empty jobs array means no work is available.

POST /ojs/v1/workers/ack

Reports that a job completed successfully.

Request body:

FieldTypeRequiredDescription
job_idstringYesThe job ID
resultanyNoReturn value from the handler

Example:

Terminal window
curl -X POST http://localhost:8080/ojs/v1/workers/ack \
-H "Content-Type: application/openjobspec+json" \
-d '{
"job_id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"result": { "delivered": true }
}'

Response (200 OK):

{
"job": {
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"state": "completed",
"completed_at": "2026-02-12T10:30:02.789Z"
}
}

Response (409 Conflict): Job is not in the active state.

POST /ojs/v1/workers/nack

Reports that a job handler failed.

Request body:

FieldTypeRequiredDescription
job_idstringYesThe job ID
errorobjectYesStructured error (see below)

Error object:

FieldTypeRequiredDescription
typestringYesError type/class name
messagestringYesHuman-readable description
backtracestring[]NoStack trace frames

Example:

Terminal window
curl -X POST http://localhost:8080/ojs/v1/workers/nack \
-H "Content-Type: application/openjobspec+json" \
-d '{
"job_id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"error": {
"type": "SmtpConnectionError",
"message": "Connection refused to smtp.example.com:587"
}
}'

Response (200 OK):

{
"job": {
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"state": "retryable",
"retry_at": "2026-02-12T10:30:05.000Z"
}
}

The server evaluates the retry policy and returns retryable (with retry_at) or discarded (if retries are exhausted).

POST /ojs/v1/workers/heartbeat

Worker sends a heartbeat to report liveness and extend visibility timeout on active jobs.

Request body:

FieldTypeRequiredDescription
worker_idstringYesThe worker’s unique ID
active_jobsstring[]NoIDs of currently active jobs

Response (200 OK):

{
"state": "running",
"jobs_extended": 2
}

The state field may be "running", "quiet", or "terminate" to signal lifecycle changes.


GET /ojs/v1/queues

Response (200 OK):

{
"queues": [
{ "name": "default", "size": 42, "paused": false },
{ "name": "email", "size": 15, "paused": false },
{ "name": "reports", "size": 3, "paused": true }
]
}
GET /ojs/v1/queues/:name/stats

Response (200 OK):

{
"queue": "default",
"size": 42,
"paused": false,
"states": {
"available": 30,
"active": 8,
"scheduled": 4
},
"throughput": {
"completed_last_minute": 120,
"failed_last_minute": 3
}
}
POST /ojs/v1/queues/:name/pause
POST /ojs/v1/queues/:name/resume

Response (200 OK):

{
"queue": "default",
"paused": true
}

GET /ojs/v1/dead-letter

Returns jobs in the discarded state (retry attempts exhausted).

Query parameters:

ParameterTypeDefaultDescription
limitinteger25Max jobs per page
cursorstringnullPagination cursor
queuestringnullFilter by queue

Response (200 OK):

{
"jobs": [
{
"id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"type": "payment.process",
"state": "discarded",
"attempt": 3,
"error": {
"type": "PaymentGatewayError",
"message": "HTTP 503: Service Unavailable"
}
}
],
"cursor": "next_page_token",
"has_more": true
}
POST /ojs/v1/dead-letter/:id/retry

Moves a discarded job back to available for re-execution.

DELETE /ojs/v1/dead-letter/:id

Permanently removes a job from the dead letter queue.


GET /ojs/v1/cron
POST /ojs/v1/cron

Request body:

{
"name": "daily-cleanup",
"type": "maintenance.cleanup",
"args": [],
"schedule": "0 2 * * *",
"timezone": "UTC",
"queue": "maintenance"
}
DELETE /ojs/v1/cron/:name

POST /ojs/v1/workflows

Request body:

{
"name": "onboarding",
"definition": {
"type": "chain",
"steps": [
{ "type": "user.create", "args": ["alice@example.com"] },
{ "type": "email.send_welcome", "args": [] },
{ "type": "analytics.track", "args": ["signup"] }
]
}
}
GET /ojs/v1/workflows/:id
DELETE /ojs/v1/workflows/:id

GET /ojs/v1/schemas
POST /ojs/v1/schemas
GET /ojs/v1/schemas/:uri
DELETE /ojs/v1/schemas/:uri

All error responses use a consistent format:

{
"error": {
"code": "invalid_request",
"message": "Job envelope validation failed: 'type' is required",
"retryable": false,
"details": { },
"request_id": "req_019414d4-0002-7000-a000-000000000001"
}
}
CodeHTTP StatusRetryableDescription
invalid_request400NoMalformed JSON or missing required fields
invalid_payload400NoJob envelope fails schema validation
schema_validation400NoArgs do not conform to registered schema
not_found404NoJob or resource does not exist
duplicate409NoUnique constraint violated
conflict409NoInvalid state transition
queue_paused503YesTarget queue is paused
rate_limited429YesRate limit exceeded
backend_error503YesBackend storage failure
timeout504YesOperation timed out
unsupported422NoFeature not supported
envelope_too_large413NoJob exceeds size limit

List endpoints support cursor-based pagination:

ParameterDescription
limitMaximum items per page (default: 25, max: 100)
cursorOpaque cursor from previous response

Responses include cursor and has_more fields for navigating pages.


When rate limited, the server returns 429 Too Many Requests with:

HeaderDescription
Retry-AfterSeconds to wait before retrying
X-RateLimit-LimitTotal requests allowed per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets