Skip to content

HTTP Protocol Binding

The HTTP protocol binding maps OJS logical operations to HTTP methods, URIs, headers, and request/response bodies. HTTP is the required baseline protocol: every networked OJS implementation must support it. Other bindings (gRPC, WebSocket, AMQP) are optional extensions.

All endpoints live under the /ojs/v1 base path. The sole exception is the conformance manifest at /ojs/manifest, which sits outside the versioned prefix so clients can discover supported versions before making versioned requests.

Each OJS core operation maps to an HTTP method and path:

OperationHTTP MethodPathStatusLevel
PUSHPOST/ojs/v1/jobs201 Created0
PUSH (batch)POST/ojs/v1/jobs/batch201 Created4
FETCHPOST/ojs/v1/workers/fetch200 OK0
ACKPOST/ojs/v1/workers/ack200 OK0
FAILPOST/ojs/v1/workers/nack200 OK0
BEATPOST/ojs/v1/workers/heartbeat200 OK1
CANCELDELETE/ojs/v1/jobs/:id200 OK1
INFOGET/ojs/v1/jobs/:id200 OK1

Worker endpoints (FETCH, ACK, FAIL, BEAT) all use POST because they carry request bodies and produce side effects. FETCH modifies queue state, so GET would be inappropriate.

The media type for OJS over HTTP is:

Content-Type: application/openjobspec+json

Servers must accept application/json as a fallback alias. All JSON bodies must be UTF-8 encoded without a byte order mark, per RFC 8259.

Clients should include an OJS-Version header to indicate the desired spec version. Servers must include OJS-Version in all responses.

Every response must include:

HeaderDescriptionExample
OJS-VersionSpec version used to process the request1.0.0-rc.1
Content-TypeResponse content typeapplication/openjobspec+json
X-Request-IdUnique request identifierreq_019414d4-8b2e-7c3a-b5d1-aaa111

POST /ojs/v1/jobs

The request body must include type and args. The args field must be a JSON array.

Terminal window
curl -s -X POST https://jobs.example.com/ojs/v1/jobs \
-H "Content-Type: application/openjobspec+json" \
-d '{
"type": "email.send",
"args": ["user@example.com", "welcome", {"locale": "en"}],
"meta": {
"trace_id": "trace_abc123def456"
},
"options": {
"queue": "email",
"priority": 0,
"retry": {
"max_attempts": 5,
"initial_interval_ms": 1000,
"backoff_coefficient": 2.0
}
}
}'

Options fields:

FieldTypeDefaultDescription
queuestring"default"Target queue name
priorityinteger0Higher values mean higher priority
timeout_msinteger30000Maximum execution time in milliseconds
delay_untilstringnullISO 8601 timestamp for delayed execution
expires_atstringnullISO 8601 deadline after which the job is discarded
retryobject(default)Retry policy override
uniqueobjectnullDeduplication policy
tagsstring[][]Tags for filtering and observability
visibility_timeout_msinteger30000Reservation period before reclaim

On success, the server responds with 201 Created and includes a Location header pointing to the new job resource.

POST /ojs/v1/jobs/batch

Enqueues multiple jobs atomically. If any job in the batch fails validation, the entire batch is rejected.

Terminal window
curl -s -X POST https://jobs.example.com/ojs/v1/jobs/batch \
-H "Content-Type: application/openjobspec+json" \
-d '{
"jobs": [
{
"type": "email.send",
"args": ["alice@example.com", "welcome", {"locale": "en"}],
"options": { "queue": "email" }
},
{
"type": "email.send",
"args": ["bob@example.com", "welcome", {"locale": "fr"}],
"options": { "queue": "email" }
}
]
}'

GET /ojs/v1/jobs/:id

Returns the full job envelope including current state, timestamps, and result or error data.

DELETE /ojs/v1/jobs/:id

Cancels a job in any non-terminal state. For active jobs, the server sets a cancellation flag the worker can check via heartbeat. Returns 409 Conflict if the job is already in a terminal state.

POST /ojs/v1/workers/fetch

Claims one or more jobs from specified queues. The server atomically transitions fetched jobs from available to active.

Terminal window
curl -s -X POST https://jobs.example.com/ojs/v1/workers/fetch \
-H "Content-Type: application/openjobspec+json" \
-d '{
"queues": ["email", "default"],
"count": 5,
"worker_id": "worker_019414d4-aaaa-7000-c000-111111111111",
"visibility_timeout_ms": 30000
}'
FieldTypeRequiredDefaultDescription
queuesstring[]YesOrdered list of queues to fetch from
countintegerNo1Maximum number of jobs to fetch
worker_idstringNoIdentifier for the worker process
visibility_timeout_msintegerNo30000Reservation period before reclaim

An empty jobs array in the response indicates no work is available. This is a normal 200 OK, not an error.

POST /ojs/v1/workers/ack

Reports successful processing. Transitions the job from active to completed. Returns 409 Conflict if the job is not in the active state.

Terminal window
curl -s -X POST https://jobs.example.com/ojs/v1/workers/ack \
-H "Content-Type: application/openjobspec+json" \
-d '{
"job_id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"result": {
"message_id": "msg_019414d5-1234-7000-b000-aabbccddeeff",
"delivered": true
}
}'

POST /ojs/v1/workers/nack

Reports that a job handler failed. The server evaluates the retry policy: if attempts remain and the error is retryable, the job moves to retryable. Otherwise it moves to discarded.

Terminal window
curl -s -X POST https://jobs.example.com/ojs/v1/workers/nack \
-H "Content-Type: application/openjobspec+json" \
-d '{
"job_id": "019414d4-8b2e-7c3a-b5d1-f0e2a3b4c5d6",
"error": {
"code": "handler_error",
"message": "SMTP connection refused: Connection timed out after 10000ms",
"retryable": true,
"details": {
"smtp_host": "mail.example.com",
"error_class": "SMTPConnectionError"
}
}
}'

The response includes the resulting state and, if retryable, the scheduled time for the next attempt.

POST /ojs/v1/workers/heartbeat

Extends the visibility timeout of active jobs and reports worker liveness. The server may respond with lifecycle directives.

DirectiveMeaning
runningNormal operation. Continue fetching and processing jobs.
quietStop fetching new jobs but finish active ones.
terminateShut down as soon as possible after finishing active jobs.

This three-state model enables zero-downtime deployments: send quiet to all workers, deploy new code, start new workers, then terminate old workers.

MethodPathDescriptionLevel
GET/ojs/v1/queuesList all queues and their status0
GET/ojs/v1/queues/:name/statsQueue statistics (job counts, throughput, latency)4
POST/ojs/v1/queues/:name/pausePause a queue (workers stop fetching)4
POST/ojs/v1/queues/:name/resumeResume a paused queue4

Pausing a queue prevents workers from fetching new jobs but does not affect jobs already being processed. Attempts to enqueue to a paused queue return 422 Unprocessable Entity with a queue_paused error code.

MethodPathDescriptionLevel
GET/ojs/v1/dead-letterList dead letter jobs (paginated)1
POST/ojs/v1/dead-letter/:id/retryRe-enqueue a dead letter job1
DELETE/ojs/v1/dead-letter/:idPermanently remove a dead letter job1

The list endpoint supports filtering by queue via ?queue=email and standard pagination parameters.

MethodPathDescriptionLevel
GET/ojs/v1/cronList all registered cron schedules2
POST/ojs/v1/cronRegister a new cron schedule2
DELETE/ojs/v1/cron/:nameRemove a cron schedule2

See Scheduling for full cron job configuration details.

MethodPathDescriptionLevel
POST/ojs/v1/workflowsCreate and start a workflow3
GET/ojs/v1/workflows/:idGet workflow status and step details3
DELETE/ojs/v1/workflows/:idCancel a workflow and its pending steps3

See Workflows for chain, group, and batch primitives.

MethodPathDescriptionLevel
GET/ojs/v1/schemasList registered schemas0
POST/ojs/v1/schemasRegister a JSON Schema for args validation0
GET/ojs/v1/schemas/:uriGet a schema by URI (URL-encoded)0
DELETE/ojs/v1/schemas/:uriRemove a schema registration0

All error responses use a consistent structure:

{
"error": {
"code": "invalid_payload",
"message": "Human-readable description of the error.",
"retryable": false,
"details": {},
"request_id": "req_019414d4-0000-7000-a000-000000000000"
}
}

The retryable field lets clients implement automated retry logic without parsing error messages.

StatusMeaningWhen Used
200OKSuccessful read, update, or command operation
201CreatedJob or resource successfully created
400Bad RequestMalformed JSON, missing required fields, invalid types
404Not FoundJob, queue, or resource does not exist
409ConflictDuplicate job (reject policy), invalid state transition
422Unprocessable EntityQueue paused, unsupported conformance level
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server-side failure
503Service UnavailableBackend is unreachable
CodeHTTP StatusRetryableDescription
invalid_request400NoMalformed HTTP request
invalid_payload400NoJob args failed schema validation
schema_validation400NoArgs do not conform to registered schema
not_found404NoRequested resource does not exist
duplicate409NoUnique job constraint violated
queue_paused422YesTarget queue is currently paused
unsupported422NoFeature requires a higher conformance level
rate_limited429YesRate limit exceeded
backend_error500YesBackend storage or transport failure

Implementations may define additional error codes prefixed with x_ (e.g., x_custom_validation). Clients should treat unrecognized codes as non-retryable.

All list endpoints support offset-based pagination:

ParameterTypeDefaultMaxDescription
limitinteger50100Maximum number of results
offsetinteger0Number of results to skip

Responses include a pagination object:

{
"pagination": {
"total": 1234,
"limit": 50,
"offset": 0,
"has_more": true
}
}

Servers that enforce rate limiting must include these headers in every response:

HeaderTypeDescription
X-RateLimit-LimitintegerMaximum requests in the current window
X-RateLimit-RemainingintegerRequests remaining in the current window
X-RateLimit-ResetintegerUnix timestamp (seconds) when the window resets

When the limit is exceeded, the server responds with 429 Too Many Requests and a Retry-After header (in seconds) per RFC 7231.

The server must generate a unique X-Request-Id for every request. If the client includes its own X-Request-Id header, the server should use that value instead. Request IDs should use the format req_<UUIDv7> and must appear both in response headers and in error response bodies as error.request_id.

GET /ojs/manifest

Returns the server’s capabilities, conformance level, and available protocol bindings. Served outside the /v1 prefix so clients can discover supported versions before making versioned requests.

{
"ojs_version": "1.0.0-rc.1",
"implementation": {
"name": "ojs-redis",
"version": "1.0.0",
"language": "go"
},
"conformance_level": 2,
"protocols": ["http"],
"backend": "redis",
"capabilities": {
"batch_enqueue": true,
"cron_jobs": true,
"dead_letter": true,
"delayed_jobs": true,
"schema_validation": true,
"unique_jobs": false,
"workflows": false
}
}

The capabilities object provides granular feature detection beyond the coarse conformance level. A Level 2 implementation might support cron jobs but not delayed jobs, and the capabilities object makes this explicit.

Authentication is out of scope for this specification. OJS does not mandate a specific mechanism because requirements vary dramatically across deployments (mTLS for internal microservices, OAuth 2.0 for public APIs, API keys for managed platforms).

Implementations should support authentication through standard HTTP headers (Authorization), mutual TLS, or custom headers prefixed with X-OJS-. When exposing OJS over public networks, TLS 1.2 or later is recommended.

OJS endpoints are primarily designed for server-to-server communication. Implementations that serve browser-based dashboards should support CORS with an explicit allowlist of trusted origins. Never set Access-Control-Allow-Origin: * on endpoints that require authentication.