Server API Reference¶
Authentication¶
Most endpoints require Bearer token authentication:
The API key is configured via the RUNQY_API_KEY environment variable on the server.
API key roles¶
Besides the admin key, two optional keys limit what a caller can do:
| Variable | Role | Can call |
|---|---|---|
RUNQY_API_KEY |
admin | Everything |
RUNQY_WORKER_API_KEY |
worker | POST /worker/register |
RUNQY_CLIENT_API_KEY |
client | POST /queue/add, POST /queue/add-batch |
GET /queue/{task_id} needs no key (the task id acts as the capability).
Give workers the worker key so a compromised machine cannot manage queues or read secrets. The autoscaler requires it: it is the key sent to rented GPU machines.
Worker Endpoints¶
POST /worker/register¶
Registers a worker and returns configuration including Redis credentials and deployment specs.
Request Headers:
| Header | Description |
|---|---|
Authorization |
Bearer {api_key} |
Content-Type |
application/json |
Request Body:
| Field | Type | Description |
|---|---|---|
worker_id |
string | Unique identifier for this worker |
queue |
string | Queue name to process tasks from |
Response:
{
"redis": {
"addr": "localhost:6379",
"password": "",
"db": 0
},
"queue": {
"name": "inference",
"priority": 6
},
"deployment": {
"git_url": "https://github.com/example/repo.git",
"branch": "main",
"startup_cmd": "python main.py",
"startup_timeout_secs": 300,
"mode": "long_running",
"vaults": ["api-keys"]
},
"vaults": {
"OPENAI_API_KEY": "sk-actual-decrypted-key",
"HF_TOKEN": "hf-actual-decrypted-token"
}
}
The vaults field in the response contains decrypted key-value pairs from all vaults referenced in the deployment configuration. These are injected as environment variables by the worker.
Error Responses:
| Status | Description |
|---|---|
| 401 | Invalid or missing API key |
| 400 | Invalid request body |
| 404 | Queue not found |
| 500 | Internal server error |
Queue Endpoints¶
POST /queue/add¶
Enqueue a new task.
Request Body:
timeout sets the execution (active) timeout
When provided, timeout (seconds) is the maximum time the task may run; exceeding it fails
the task and retries it (up to max_retry). It overrides the queue's active_timeout. Omit it
to use the queue default. All other lifecycle values (TTL, retries, pending timeout) come from
the queue config — see Task Lifecycle.
Response:
POST /queue/add-batch¶
Enqueue multiple tasks in a single request. Uses Redis pipelining for high-throughput job submission.
Request Body:
{
"queue": "inference.high",
"timeout": 300,
"jobs": [
{"data": {"prompt": "Hello"}},
{"data": {"prompt": "World"}},
{"data": {"prompt": "Foo"}}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
queue |
string | Yes | Target queue name |
timeout |
int | No | Execution (active) timeout applied to every job, in seconds. Overrides the queue's active_timeout. Omit to use the queue default |
jobs |
array | Yes | Array of job objects |
jobs[].data |
object | Yes | Task payload |
Lifecycle limits come from the queue
TTL, retries, and pending timeout are taken from the target queue's configuration; only the
execution (active) timeout can be overridden per request via timeout. See
Task Lifecycle.
Response:
| Field | Type | Description |
|---|---|---|
enqueued |
int | Number of successfully enqueued tasks |
failed |
int | Number of failed tasks |
task_ids |
array | List of task IDs (in order) |
errors |
array | Error messages for failed tasks |
Performance:
| Method | Throughput |
|---|---|
Single POST /queue/add calls |
~800-1,000 jobs/s |
| Batch endpoint (100 jobs/request) | ~35,000-50,000 jobs/s |
Optimal batch size
Batch sizes of 50-200 jobs per request offer the best balance of throughput and latency.
GET /queue/{task_id}¶
Get task status and result. The queue is automatically determined from the task's metadata stored in Redis.
Response:
Error Responses:
| Status | Description |
|---|---|
| 404 | Task not found |
| 400 | Invalid request |
| 500 | Internal server error |
CLI API Endpoints¶
These endpoints are used by the CLI for remote management. All require Bearer token authentication.
Queue Management¶
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/cli/queues |
List all queues with statistics |
GET |
/api/cli/queues/{queue} |
Get detailed queue information |
POST |
/api/cli/queues/{queue}/pause |
Pause a queue |
POST |
/api/cli/queues/{queue}/unpause |
Unpause a queue |
Task Management¶
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/cli/tasks |
Enqueue a new task |
GET |
/api/cli/queues/{queue}/tasks |
List tasks in a queue |
GET |
/api/cli/queues/{queue}/tasks/{task_id} |
Get task details |
DELETE |
/api/cli/tasks/{task_id} |
Cancel a task |
DELETE |
/api/cli/queues/{queue}/tasks/{task_id} |
Delete a task |
Worker Management¶
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/cli/workers |
List all workers |
GET |
/api/cli/workers/{worker_id} |
Get worker details |
Config Management¶
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/cli/configs |
List all queue configurations |
POST |
/api/cli/configs/reload |
Reload configurations from YAML |
POST |
/api/cli/configs |
Create a new queue configuration |
DELETE |
/api/cli/configs/{name} |
Delete a queue configuration |
Vault Endpoints¶
Vault endpoints manage encrypted secrets. All require Bearer token authentication.
Prerequisite
The vaults feature must be enabled by setting the RUNQY_VAULT_MASTER_KEY environment variable. If not set, vault endpoints return 503 Service Unavailable.
GET /api/vaults¶
List all vaults with entry counts.
Response:
{
"vaults": [
{
"name": "api-keys",
"description": "API keys for external services",
"entry_count": 3
}
],
"count": 1
}
POST /api/vaults¶
Create a new vault.
Request Body:
Response:
{
"message": "vault created successfully",
"vault": {
"name": "api-keys",
"description": "API keys for external services"
}
}
GET /api/vaults/{name}¶
Get vault details with entries (secret values masked).
Response:
{
"name": "api-keys",
"description": "API keys for external services",
"entries": [
{
"key": "OPENAI_API_KEY",
"value": "sk****yz",
"is_secret": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T12:45:00Z"
}
],
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T12:45:00Z"
}
DELETE /api/vaults/{name}¶
Delete a vault and all its entries.
Response:
POST /api/vaults/{name}/entries¶
Set or update a vault entry.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | Entry key (environment variable name) |
value |
string | Yes | Entry value |
is_secret |
boolean | No | Mask value in API responses (default: true) |
Response:
GET /api/vaults/{name}/entries¶
List all entries in a vault (secret values masked).
Response:
{
"entries": [
{
"key": "OPENAI_API_KEY",
"value": "sk****yz",
"is_secret": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T12:45:00Z"
}
],
"count": 1
}
DELETE /api/vaults/{name}/entries/{key}¶
Delete a vault entry.
Response:
Autoscale Endpoints¶
GPU autoscaling status and provider management. Admin key required. Provider endpoints need
RUNQY_VAULT_MASTER_KEY (credentials are stored encrypted). See the
Autoscaling guide.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/autoscale/status |
Tracked instances, states and accumulated cost |
POST |
/api/autoscale/instances/{id}/protect |
Protect an instance from scale-down |
POST |
/api/autoscale/instances/{id}/unprotect |
Remove the protection |
GET |
/api/autoscale/provider-types |
Available provider types (mock, runpod, vastai) |
GET |
/api/autoscale/providers |
List providers (secrets masked) |
POST |
/api/autoscale/providers |
Create a provider |
GET |
/api/autoscale/providers/{name} |
Get a provider (secrets masked) |
PUT |
/api/autoscale/providers/{name} |
Update a provider |
DELETE |
/api/autoscale/providers/{name} |
Delete a provider |
POST /api/autoscale/providers body:
{
"name": "gpu-runpod",
"provider_type": "runpod",
"config": { "api_key": "rpa_xxx", "gpu_type_id": "NVIDIA GeForce RTX 4090" },
"enabled": true
}
PUT is a partial update: omit config to keep the stored configuration, omit enabled to keep
the current flag. A secret sent back in its masked form (as returned by GET) keeps its stored value.
GET /api/autoscale/status response:
{
"instances": [
{
"instance_id": "as-231d7097-…",
"provider": "gpu-runpod",
"queue": "inference",
"status": "running",
"worker_id": "a1b2c3:7",
"protected": false,
"price_per_hour": 0.6,
"cost_accumulated": 0.412,
"created_at": "2026-10-14T10:02:11Z",
"last_job_at": "2026-10-14T10:20:40Z"
}
],
"count": 1,
"total_cost": 0.412
}
Alert Endpoints¶
Email alerting. Admin key required. See the Alerts guide.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/alerts?history=50 |
Active alerts, recent notifications, evaluator status |
GET |
/api/alerts/settings |
Global alert settings (literal secrets masked) |
PUT |
/api/alerts/settings |
Replace the global alert settings |
POST |
/api/alerts/test |
Send a test email |
GET /api/alerts response:
{
"enabled": true,
"active": [
{
"queue": "inference",
"rule": "no_workers",
"firing": true,
"since": "2026-10-14T09:58:21Z",
"fired_at": "2026-10-14T09:58:21Z",
"message": "no live worker is serving the queue (3 job(s) pending)"
}
],
"history": [
{
"time": "2026-10-14T09:58:21Z",
"queue": "inference",
"rule": "no_workers",
"status": "firing",
"message": "no live worker is serving the queue (3 job(s) pending)"
}
],
"status": {
"last_eval": "2026-10-14T09:58:51Z",
"last_sent": "2026-10-14T09:58:21Z"
}
}
active lists conditions that are firing, and those still waiting for their for duration
(firing: false). History statuses are firing, repeat, resolved and event.
PUT /api/alerts/settings body: the same fields as the YAML alerts block, in JSON:
{
"enabled": true,
"provider": "smtp",
"from": "runqy <alerts@example.com>",
"recipients": ["ops@example.com"],
"smtp": { "host": "smtp.example.com", "port": 587, "tls": "starttls", "username": "alerts@example.com", "password": "vault://mail/smtp_password" },
"rules": [{ "type": "no_workers", "for": "2m" }, { "type": "latency", "max_latency": "10m" }],
"repeat_interval": "4h",
"send_resolved": true
}
Invalid settings return 400 with the reason. A secret sent back masked keeps its stored value.
POST /api/alerts/test: with an empty body, uses the saved settings; with a settings body,
tests those (unsaved) settings. Returns 502 with the delivery error if the email could not be sent.
Control Endpoint¶
Send a command to workers. Admin key required. See the Worker Control guide.
POST /api/control
| Field | Description |
|---|---|
action |
drain, resume, code_update, worker_update or config_reload |
target |
Worker id or queue name; omitted: every worker |
version |
Release to install, e.g. 0.4.1 (worker_update only, required) |
timeout |
How long to wait for tasks in flight (Go duration) |
Response 202 Accepted:
{
"id": "2223310d948b29a8",
"action": "code_update",
"channel": "runqy:control:inference",
"target": "queue inference",
"receivers": 2
}
receivers is the number of workers that got the command (0: none is listening). The outcome
appears in each worker's last_command in GET /workers/{id}, next to version,
control_actions and held. An unknown action, a missing version or an unknown target returns 400.
Swagger Documentation¶
Interactive API documentation is available at: