Hub API Reference
The Sevorix Hub is the account and artifact backend behind hub.sevorix.com. Its REST API is what an external integrator — or the sevorix/sevsh CLI's own hub client — calls to register an account, manage signing keys, and publish or search artifacts.
Base URL
https://hub.sevorix.comEvery path in this reference is relative to that origin, and every API path is prefixed /api/v1. A self-hosted or preview instance serves the same routes under its own hostname — substitute it for the base URL and nothing else in these examples changes.
Conventions
- Requests and responses are JSON. Any endpoint that takes a body expects
Content-Type: application/json; a body the server cannot deserialize is rejected with400. - Authentication is a bearer token, never a cookie. See Authentication.
- Timestamps are RFC 3339 / ISO 8601 in UTC, e.g.
2026-03-14T09:21:07.481293Z. - Identifiers (
id,user_id,artifact_id) are UUIDs, rendered as lowercase hyphenated strings. - Absent values are
null, not omitted — an optional field is present in the response with anullvalue.
Error responses
Every error the API itself raises has the same body shape:
{ "error": "artifact 'net-egress@1.0.0' already exists" }The message is written to be shown to a person. A client should prefer it over anything it could reconstruct from the status code alone.
Two failures do not use that shape, and a client that assumes JSON will mis-handle both:
429 Too Many Requestscomes from the rate limiter in front of the handler. Its body is plain text with noContent-Typeheader at all.- Malformed path parameters (a
:user_idthat is not a UUID, say) are rejected by the framework's own extractor before the handler runs, with a plain-text400.
Parse defensively: attempt JSON, fall back to the raw body, fall back to the status.
Status codes
The hub is deliberate about which status it returns, and the distinctions carry information a caller can act on.
| Status | Meaning here |
|---|---|
200 OK | Success, with a body. |
201 Created | A new resource was created (register, push, key, endorsement). |
202 Accepted | The request was accepted; the effect is asynchronous. Only password-reset requests return this. |
204 No Content | Success, with no body (deletes, revocations, yanks). |
400 Bad Request | The request itself is wrong: a malformed body, a field that fails validation, a value out of range. |
401 Unauthorized | You have not established who you are, or a credential you supplied did not check out. See 401 vs 403. |
403 Forbidden | You are known, and not permitted to do this. Retrying with the same account will not help. |
404 Not Found | The resource does not exist — or it exists and is not yours to see. See 404 as a privacy boundary. |
409 Conflict | A uniqueness constraint was violated: an email already registered, an artifact version already published, a key already known, an artifact already endorsed by you. |
429 Too Many Requests | A rate limit was hit. Plain-text body — see above. |
500 Internal Server Error | An unhandled failure. The body is always the generic "internal server error"; details are logged server-side only. |
503 Service Unavailable | A dependency this endpoint needs is not configured on this instance. Only password-reset requests return this. |
401 and 403 are not interchangeable in this API, and neither is 404 where artifacts are concerned. All three are covered in detail on the Authentication page.
Rate limiting
Limits are per client IP address, enforced as token buckets in front of the handlers. A request that exceeds one never reaches the handler.
| Scope | Limit |
|---|---|
POST /api/v1/login | 10 requests/minute |
PATCH /api/v1/me/password | 10 requests/minute |
POST /api/v1/password-reset/confirm | 10 requests/minute |
POST /api/v1/register | 5-request burst, refilling at 1/minute |
POST /api/v1/password-reset/request | 5-request burst, refilling slowly |
| Everything else | 1000-request burst, refilling at ~17/second |
The stricter buckets guard endpoints that run an Argon2 hash on caller-supplied input, or that cost an outbound email. Password resets are additionally capped per account at 3 tokens per hour, independently of which IP asks — see Password reset.
Browser callers (CORS)
The hub is called directly from a browser frontend on a different origin, so it sends CORS headers — but from a compiled-in allow-list of origins, not a wildcard. A page served from an origin that is not on that list will have its requests blocked by the browser regardless of whether the request itself would have succeeded.
Access-Control-Allow-Credentials is deliberately not sent. Authentication here is a bearer header and never a cookie, so there is no ambient credential for a cross-origin request to carry.
Liveness
GET /healthUnauthenticated. Returns 200 with an empty body as long as the process is up and serving. It deliberately does not touch the database, so it stays meaningful when the database or billing dependencies are degraded — it answers "is this process alive", not "is this service healthy end to end".
In this section
- Authentication — obtaining a token, presenting it, password rules, the reset flow, and what each kind of rejection means.
- Endpoints — method, path, request and response shapes, and error responses for every public route.