REST API reference
Endpoints, JWT authentication, curl examples, and error patterns of the HeroCtl API.
The HeroCtl API is the single channel between you, the CLI, and the web interface. Everything the panel shows passes through here. Everything heroctl does on the command line too.
The API is REST. JSON in, JSON out. No GraphQL. No public gRPC.
Base endpoint
https://manage.<seu-dominio>/v1/
In standard production: https://manage.heroctl.com/v1/.
All routes live under /v1/. When v2 arrives, v1 keeps responding for at least 12 months.
Authentication
The API uses JWT. You obtain a token via login and send it on every request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
No header, no response. The server replies 401.
Login
curl -X POST https://manage.heroctl.com/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
Response:
{
"token": "eyJhbGciOi...",
"expires_at": "2026-04-27T15:00:00Z",
"refresh_token": "rt_8f3a..."
}
The default token lasts 24 hours.
You can also authenticate with an ACL token (generated through the interface or the CLI):
curl -X POST https://manage.heroctl.com/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"acl_token":"ht_a1b2c3d4..."}'
Refresh
Before the token expires, swap it for a new one:
curl -X POST https://manage.heroctl.com/v1/auth/refresh \
-H "Authorization: Bearer $TOKEN"
Main endpoints
Jobs
A job is the declarative definition of what should run. The cluster takes care of the rest.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/jobs | Lists all jobs |
| GET | /v1/jobs/:name | Detail of a job |
| POST | /v1/jobs | Submits or updates a job |
| DELETE | /v1/jobs/:name | Removes the job and its instances |
List jobs:
curl -s https://manage.heroctl.com/v1/jobs \
-H "Authorization: Bearer $TOKEN" | jq '.[] | .name'
Submit a job:
curl -X POST https://manage.heroctl.com/v1/jobs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @meu-job.json
Allocations
Each replica of a job becomes an allocation. It's the actual unit of execution.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/allocations | Lists allocations |
| GET | /v1/allocations/:id | Detail |
| POST | /v1/allocations/:id/stop | Stops an allocation |
curl -s "https://manage.heroctl.com/v1/allocations?job=heroctl-site" \
-H "Authorization: Bearer $TOKEN" | jq '.[] | {id, node, status}'
Nodes
The servers that run workloads.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/nodes | Lists nodes |
| GET | /v1/nodes/:id | Detail (cpu, ram, disk, status) |
| POST | /v1/nodes/:id/drain | Drains a node (moves workloads off) |
Drain a node before maintenance:
curl -X POST https://manage.heroctl.com/v1/nodes/server-2/drain \
-H "Authorization: Bearer $TOKEN" \
-d '{"deadline_seconds": 300}'
Warning: drain removes all allocations from the node. Make sure capacity exists on the others first.
Cluster
| Method | Route | What it does |
|---|---|---|
| GET | /v1/cluster/status | Overall health, coordinator node, member count |
| GET | /v1/cluster/peers | List of control plane peers |
| POST | /v1/raft/transfer-leadership | Transfers coordination (admin) |
curl -s https://manage.heroctl.com/v1/cluster/status \
-H "Authorization: Bearer $TOKEN" | jq
Secrets
Sensitive values. Stored encrypted at rest.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/secrets | Lists names (never values) |
| POST | /v1/secrets | Creates or updates |
| GET | /v1/secrets/:name | Reads a value (requires specific ACL) |
| DELETE | /v1/secrets/:name | Deletes |
Create a secret:
curl -X POST https://manage.heroctl.com/v1/secrets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"db_password","value":"s3nh@-forte"}'
Metrics
| Method | Route | What it does |
|---|---|---|
| GET | /v1/metrics | Current snapshot (CPU, mem, allocs, ingress) |
| GET | /v1/metrics/query | Historical query with time window |
curl -s "https://manage.heroctl.com/v1/metrics/query?metric=cpu&job=heroctl-site&from=-1h" \
-H "Authorization: Bearer $TOKEN" | jq
Logs
Logs live per allocation.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/logs/:alloc | Last N lines |
| GET | /v1/logs?stream=true | Streaming via SSE |
Live streaming:
curl -N "https://manage.heroctl.com/v1/logs?job=heroctl-site&stream=true" \
-H "Authorization: Bearer $TOKEN"
The output comes in text/event-stream format:
event: log
data: {"alloc":"a1b2","line":"server started"}
Health
Endpoints without authentication. For external monitoring use.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/health/ready | Ready to accept traffic |
| GET | /v1/health/live | Process alive |
curl -s https://manage.heroctl.com/v1/health/ready
# {"status":"ok"}
Ingress
Domains served by the cluster.
| Method | Route | What it does |
|---|---|---|
| GET | /v1/ingress | All active domains |
| GET | /v1/ingress/:host | Detail of a domain (cert, routes, target job) |
curl -s https://manage.heroctl.com/v1/ingress/heroctl.com \
-H "Authorization: Bearer $TOKEN" | jq
Long polling
Several endpoints support blocking waits. Instead of polling every second, you pass an index and a maximum wait time. The response returns when something changes — or when the time runs out.
curl -s "https://manage.heroctl.com/v1/jobs?wait=5m&index=42" \
-H "Authorization: Bearer $TOKEN"
index=N— index of the last version you saw.wait=5m— max wait time (max 10m).
If nothing changes in 5 minutes, the response comes back with the same index. Reuse the index in the next call.
This dramatically reduces traffic and latency on live panels.
Rate limiting
Each token has a limit of 1000 requests per minute. Above that the server replies:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
The Retry-After header indicates in seconds when you can try again.
Admin tokens have a separate, more generous limit. For heavy automation, request a dedicated service token.
Error codes
| Status code | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 400 | Invalid request (malformed JSON, missing field) |
| 401 | Token missing, expired, or invalid |
| 403 | Token valid, but no permission for the route |
| 404 | Resource does not exist |
| 409 | Conflict (e.g., creating a job with an existing name) |
| 429 | Rate limit hit |
| 500 | Internal error |
| 503 | Cluster temporarily without coordination |
Errors come with a standardized body:
{
"error": "job_not_found",
"message": "job 'foo' não existe",
"request_id": "req_a1b2c3"
}
Always include the request_id when you open a support ticket.
Webhooks (Business plan)
You register a URL and the cluster sends POSTs on relevant events.
curl -X POST https://manage.heroctl.com/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-d '{
"url": "https://api.minha-empresa.com/heroctl-events",
"events": ["job.deployed", "alloc.failed", "node.down"],
"secret": "whk_a1b2c3"
}'
Available events:
job.submitted— new job submittedjob.deployed— all replicas healthyjob.failed— deploy failed and rolled backalloc.failed— an instance went downnode.down— a server left the clustercert.renewed— TLS certificate renewedsecret.changed— secret value updated
Each POST carries an X-HeroCtl-Signature header with HMAC-SHA256 of the body, using the secret you registered. Validate before processing.
Next steps
- See authentication and ACL to create scoped tokens.
- See troubleshooting if a call returns unexpected errors.
- See backup and restore to protect the state the API exposes.